--------------------2015-3-11--------------------
"#'"的意思是“获取函数,其名如下”的简称。如果没有"#'",Lisp会将evenp(或其他)作为一个变量名来对待并查找该变量的值,而不是将其看作函数。
在一份反引用(`))表达式里,任何以逗号(,)开始的子表达式都是被求值的。
如:`(1 2 (+ 1 2)) → (1 2 (+ 1 2))
`(1 2 ,(+ 1 2)) → (1 2 3)
",@"可以将接下来的表达式(必须求值成一个列表)的值嵌入到其外围的列表里。
如:`(and ,(list 1 2 3)) → (AND (1 2 3))
`(and ,@(list 1 2 3)) → (AND 1 2 3)
也可以使用",@"在列表的中间插入东西
`(and ,@(list 1 2 3) 4) → (AND 1 2 3 4)
MACROEXPAND-1函数:
传入macroexpand-1一个代表宏调用的形式,它将使用适当的参数来调用宏代码并返回其展开式。
如:CL-USER> (macroexpand-1 '(where :title "Give Us a Break" :ripped t))
#'(LAMBDA (CD)
(AND (EQUAL (GETF CD :TITLE) "Give Us a Break")
(EQUAL (GETF CD :RIPPED) T)))
T
S-表达式:
S-表达式的基本元素是列表(list)和原子(atom)。
注释以";"开始,直到一行的结尾,本质上将当作空白来处理。
数字:
123 ; 整数一百二十三
3/7 ; 比值七分之三
1.0 ; 默认精度的浮点数一
1.0e0 ; 同一个浮点数的另一种写法
1.0d0 ; 双精度的浮点数一
1.0e-4 ; 等价于万分之一的浮点数
+42 ; 整数四十二
-42 ; 整数负四十二
-1/4 ; 比值负四分之一
-2/8 ; 负四分之一的另一种写法
246/2 ; 整数一百二十三的另一种写法
字符串:
两个在字符串中必须被转义的字符是双引号和反斜杠本身。
"foo" ; 含有f、o和o的字符串
"fo\o" ; 同一个字符串
"fo\\o" ; 含有f、o、\和o的字符串
"fo\"o" ; 含有f、o、"和o的字符串
名字:
几乎任何字符都可以出现在一个名字里,不过空白字符除外,因为列表的元素是用空格来分隔的。
有十个字符被用于其他句法目的而不能出现在名字里,它们是:开括号和闭括号([])、双引号和单引号(""、'')、反引号(`)、逗号(,)、冒号(:)、分号(;)、反斜杠(\)以及竖线(|)。
而就算这些字符,如果你愿意的话,它们也能成为名字的一部分,只需将他们用反斜杠进行转义,或是将含有需要转义的字符名字用竖线包起来。
当读取器读取名字时,它将所有名字中未转义的字符都转化成它们等价的大写形式。如;foo、Foo和FOO都读成同一个符号:FOO
但是\f\o\o和|foo|都将被读成foo,是和符号FOO不同的另一个对象。
约定:
全局变量以"*"开始和结尾,如:*global-variable*
常量名都以"+"开始和结尾,如:+const-variable+
而某些程序员则将特别底层的函数名前加"%"甚至"%%"
语言标准所定义的名字只是用字母表字符(A-Z)外加*、+、-、/、1、2、<、=、>以及&
简单的S-表达式示例:
x ; 符号X
() ; 空列表
(1 2 3) ; 三个数字所组成的列表
("foo" "bar") ; 两个字符串所组成的列表
(x y z) ; 三个符号所组成的列表
(x 1 "foo") ; 由一个符号、一个数字和一个字符串所组成的列表
(+ (* 2 3) 4) ; 由一个符号、一个列表和一个数字所组成的列表
(defun hello-world ()
(format t "hello, world"))
; 由两个符号、空列表和一个列表——其本身又含两个符号和一个字符串所组成的列表
函数调用:
(function-name argument*)
特殊操作符:
(1)IF:
(if test-form then-form [else-form])
其中test-form是条件,then-form是条件值为真时求值的表达式,else-form是条件值为假时求值的表达式。
(2)QUOTE:
(quote statement)
它接受一个单一的表达式作为其“参数”并简单地返回它,不经求值。
如:(quote (+ 1 2)) → (+ 1 2)
由于 QUOTE 被用得十分普遍,以至于读取器中内置了一个它的特殊的语法形式:
(quote (+ 1 2)) 等价于 '(+ 1 2)
(3)LET:
(let statement)
它用来创建新的变量绑定。
如:(let ((x 10)) x)
求值得到10,因为在第二个x的求值环境中,它是由 LET 赋值为10的变量名。
真、假和等价:
符号 NIL 是唯一的假值,其他所有的都是真值。
符号 T 是标准的真值,可用于需要返回一个非 NIL 值却又没有其他值可用的情况。
关于 NIL 它是唯一一个既是原子(atom)又是列表(list)的对象,除了用来表示假以外,它还用来表示空列表。
因为 NIL 是一个以符号 NIL 作为其值的常值变量名,所以表达式 nil、()、'nil 以及'()求值结果是相同的
基于同样的道理 t 和 't 的求值结果也完全相同:符号 T 。
四个“通用”等价谓词:
EQ:
EQ 用来测试“对象标识”,只有当两个对象相同时才是 EQ 等价的。不幸的是,数字和字符的对象标识取决于这些数据类型在特定Lisp平台上的实现的方式。因此带有相同值的两个数字或字符可能会被 EQ 认为是等价的也可能会是不等价的。
EQL:
EQL 和 EQ 有相似的行为,除此之外,它也可以保证当相同类型的两个对象表示相同的数字或字符时,它们是等价的。
因此 (eql 1 1) 能确保值为真,而 (eql 1 1.0)则被确保是假,因为整数 1 和浮点数 1.0 是不同类型的对象。
EQUAL:
EQUAL 相比 EQL 的宽松之处在于,它将在递归上具有相同结构和内容的列表视为等价。
EQUAL 也认为含有相同字符的字符串是等价的。
它对于位向量和路径名也定义了比 EQL 更加宽松的等价性。
EQUALP:
更加宽松,并且在考察两个含有相同字符的字符串的等价性时忽略了大小写的区别。
它还认为如果两个字符只是大小写上有区别,那么它们就是等价的。
只要数字表示相同数学意义上的值,它们也是等价的。
因此 (equalp 1 1.0) 值为真。
函数:
函数定义:
(defun name (parameter*)
"Optional documentation string."
boby-form*)
任何符号都可用作函数名。通常函数名仅包含字典符和连字符,但是在特定的命名约定里,其他字符也允许使用。
如果一个字符串紧跟在形参列表之后,那么它应该是一个用来描述函数用途的文档字符串。当定义函数时,该文档字符串将被关联到函数名上,并且以后可以通过 DOCUMENTATION 函数来获取。
例如:(documentation 'foo 'function) 将返回 foo 的文档字符串。
一个 DEFUN 的主体可由任意数量的 Lisp 表达式所构成,它们将在函数被调用时依次求值,而最后一个表达式的值将被作为整个函数的值返回。另外 RETURN-FROM 特殊操作符可用于从函数的任何位置立即返回。
一个复杂的函数示例:
(defun verbose-sum (x y)
"Sum any two numbers after printing a message."
(format t "Summing ~d and ~d.~%" x y)
(+ x y))
可选形参:
为了定义一个带有可选形参的函数,在必要形参的名字之后放置符号 &optional ,后接可选形参的名字。
如:(defun foo (a b &optional c d) (list a b c d))
调用结果:
(foo 1 2) → (1 2 NIL NIL)
(foo 1 2 3) → (1 2 3 NIL)
(foo 1 2 3 4) → (1 2 3 4)
如果想用一个特定的值来指定为默认值,则可以用一个表达式来实现:
(defun foo (a &optional (b 10)) (list a b))
调用结果:
(foo 1 2) → (1 2)
(foo 1) → (1 10)
如果需要更灵活地选择默认值。比如可能想要基于其他形参来计算默认值。默认值表达式可以引用早先出现在形参列表中的形参。如果要编写一个返回矩形的某种表示的函数,并且想要使它可以特别方便地产生正方形,那么可以使用如下形式:
(defun make-rectangle (width &optional (height width)) ...)
有时,有必要去了解一个可选形参的值究竟是被调用者明确指定还是使用了默认值。可以通过在形参标识符的默认值表达式之后添加另一个变量名来做到,该变量将在调用者实际为该形参提供了一个实参时绑定到真值,否则为 NIL 。通常约定,这种变量的名字与对应的真实形参相同,但带有一个 -supplied-p 后缀,如:
(defun foo (a b &optional (c 3 c-supplied-p))
(list a b c c-supplied-p))
调用结果:
(foo 1 2) → (1 2 3 NIL)
(foo 1 2 3) → (1 2 3 T)
(foo 1 2 4) → (1 2 4 T)
剩余形参:
Lisp 允许在符号 &rest 之后包括一揽子形参。如果函数带有 &rest 形参,那么任何满足了必要和可选形参之后的其余所有实参就将被收集到一个列表里成为该 &rest 形参的值。 这样,FORMAT 和 + 的形参列表可能看起来会是这样:
(defun format (stream string &rest values) ...)
(defun + (&rest numbers) ...)
关键字形参:
它允许调用者指定具体形参相应所使用的值。
为了使函数带有关键字形参,在任何必要的 &optional 和 &rest 形参之后,可以加上符号 &key 以及任意数量的关键字形参标识符,后者的格式类似于可选形参标识符。
如:(只有关键字形参的函数)
(defun foo (&key a b c) (list a b c))
如果一个给定的关键字没有出现在实参列表中,那么对应的形参将被赋予其默认值,如同可选形参那样。
调用结果:
(foo) → (NIL NIL NIL)
(foo :a 1) → (1 NIL NIL)
(foo :b 1) → (NIL 1 NIL)
(foo :c 1) → (NIL NIL 1)
(foo :a 1 :c 3) → (1 NIL 3)
(foo :a 1 :b 2 :c 3) → (1 2 3)
(foo :c 3 :a 1 :b 2) → (1 2 3)
如同可选形参一样,关键字形参也可以提供一个默认值形式以及一个 -supplied-p 变量名。这个默认值都可以引用早先出现在形参列表中的形参。
(defun foo (&key (a 0) (b 0 b-supplied-p) (c (+ a b)))
(list a b c b-supplied-p))
调用结果:
(foo :a 1) → (1 0 1 NIL)
(foo :b 1) → (0 1 1 T)
(foo :b 1 :c 4) → (0 1 4 T)
(foo :a 2 :b 1 :c 4) → (2 1 4 T)
如果想要让调用者用来指定形参的关键字不同于实际形参名,可以将形参名替换成一个列表,令其含有调用函数时使用的关键字以及用作形参的名字。
(defun foo (&key ((:apple a)) ((:box b) 0) ((:charlie c) 0 c-supplied-p))
(list a b c c-supplied-p))
调用结果:
(foo :apple 10 :box 20 :charlie 30) → (10 20 30 T)
混合不同的形参类型:
当用到多种类型的形参时,它们必须以这样的顺序声明:
(1)必要形参
(2)可选形参
(3)剩余形参
(4)关键字形参
一般不会将 &optional 或者 &rest 和 &key 形参组合使用。
函数返回值:
RETURN-FROM 特殊操作符,它能立即以任何值从函数中间返回。
它还可以从一个由 BLOCK 特殊操作符所定义的代码块中返回。
(defun foo (n)
(dotimes (i 10)
(dotimes (j 10)
(when (> (* i j) n)
(return-from foo (list i j))))))
高阶函数——作为数据的函数:
特殊操作符 FUNCTION 提供了用来获取一个函数对象的方法。它接受单一实参并返回与该参数同名的函数。这个名字是不被引用的。因此如果一个函数 foo 的定义如下:
CL-USER> (defun foo (x) (* 2 x))
FOO
CL-USER> (function foo)
#<Interpreted Function FOO>
如同 (') 是 QUOTE 的语法糖一样。(#') 是 FUNCTION 的语法糖。因此:
CL-USER> #'foo
#<Interpreted Function FOO>
Common Lisp 提供了两个函数用来通过函数对象调用函数: FUNCALL 和 APPLY。
它们的区别仅在于如何获取传递给函数的实参。
FUNCALL:
用于在编写代码时确切知道传递给函数多少实参时。FUNCALL 的第一个实参是被调用的函数对象,其余的实参被传递到该函数中。
(foo 1 2 3) ≡ (funcall #'foo 1 2 3)
下面这个函数演示了 FUNCALL 的另一个更有建设性的用法。它接受一个函数对象作为实参,并使用实参函数在 min 和 max 之间以 step 为步长的返回值来绘制一个简单的ASCII式柱状图:
(defun plot (fn min max step)
(loop for i from min to max by step do
(loop repeat (funcall fn i) do (format t "*"))
(format t "~%")))
APPLY:
和 FUNCALL 一样,APPLY 的第一个参数是一个函数对象。但在这个函数对象之后,它期待一个列表而非单独的实参。它将函数应用在列表的值上:
(apply #'plot plot-data) ; 假如该函数需要的参数以列表的形式存在 plot-data 中
更方便的是,APPLY 还接受“孤立”(loose)的实参,只要最后一个参数是一个列表就行:
(apply #'plot #'exp plot-data)
匿名函数:
(lambda (parameters) body)
可以这么用:
(funcall #'(lambda (x y) (+ x y)) 2 3) → 5
还可以这么用
((lambda (x y) (+ x y)) 2 3) → 5 ; 几乎没人这么做
示例:
CL-USER> (plot #'(lambda (x) (* 2 x)) 0 10 1)
LAMBDA 表达式的另一项重要用途是制作闭包 (closure),即捕捉了其创建时环境信息的函数。
"#'"的意思是“获取函数,其名如下”的简称。如果没有"#'",Lisp会将evenp(或其他)作为一个变量名来对待并查找该变量的值,而不是将其看作函数。
在一份反引用(`))表达式里,任何以逗号(,)开始的子表达式都是被求值的。
如:`(1 2 (+ 1 2)) → (1 2 (+ 1 2))
`(1 2 ,(+ 1 2)) → (1 2 3)
",@"可以将接下来的表达式(必须求值成一个列表)的值嵌入到其外围的列表里。
如:`(and ,(list 1 2 3)) → (AND (1 2 3))
`(and ,@(list 1 2 3)) → (AND 1 2 3)
也可以使用",@"在列表的中间插入东西
`(and ,@(list 1 2 3) 4) → (AND 1 2 3 4)
MACROEXPAND-1函数:
传入macroexpand-1一个代表宏调用的形式,它将使用适当的参数来调用宏代码并返回其展开式。
如:CL-USER> (macroexpand-1 '(where :title "Give Us a Break" :ripped t))
#'(LAMBDA (CD)
(AND (EQUAL (GETF CD :TITLE) "Give Us a Break")
(EQUAL (GETF CD :RIPPED) T)))
T
S-表达式:
S-表达式的基本元素是列表(list)和原子(atom)。
注释以";"开始,直到一行的结尾,本质上将当作空白来处理。
数字:
123 ; 整数一百二十三
3/7 ; 比值七分之三
1.0 ; 默认精度的浮点数一
1.0e0 ; 同一个浮点数的另一种写法
1.0d0 ; 双精度的浮点数一
1.0e-4 ; 等价于万分之一的浮点数
+42 ; 整数四十二
-42 ; 整数负四十二
-1/4 ; 比值负四分之一
-2/8 ; 负四分之一的另一种写法
246/2 ; 整数一百二十三的另一种写法
字符串:
两个在字符串中必须被转义的字符是双引号和反斜杠本身。
"foo" ; 含有f、o和o的字符串
"fo\o" ; 同一个字符串
"fo\\o" ; 含有f、o、\和o的字符串
"fo\"o" ; 含有f、o、"和o的字符串
名字:
几乎任何字符都可以出现在一个名字里,不过空白字符除外,因为列表的元素是用空格来分隔的。
有十个字符被用于其他句法目的而不能出现在名字里,它们是:开括号和闭括号([])、双引号和单引号(""、'')、反引号(`)、逗号(,)、冒号(:)、分号(;)、反斜杠(\)以及竖线(|)。
而就算这些字符,如果你愿意的话,它们也能成为名字的一部分,只需将他们用反斜杠进行转义,或是将含有需要转义的字符名字用竖线包起来。
当读取器读取名字时,它将所有名字中未转义的字符都转化成它们等价的大写形式。如;foo、Foo和FOO都读成同一个符号:FOO
但是\f\o\o和|foo|都将被读成foo,是和符号FOO不同的另一个对象。
约定:
全局变量以"*"开始和结尾,如:*global-variable*
常量名都以"+"开始和结尾,如:+const-variable+
而某些程序员则将特别底层的函数名前加"%"甚至"%%"
语言标准所定义的名字只是用字母表字符(A-Z)外加*、+、-、/、1、2、<、=、>以及&
简单的S-表达式示例:
x ; 符号X
() ; 空列表
(1 2 3) ; 三个数字所组成的列表
("foo" "bar") ; 两个字符串所组成的列表
(x y z) ; 三个符号所组成的列表
(x 1 "foo") ; 由一个符号、一个数字和一个字符串所组成的列表
(+ (* 2 3) 4) ; 由一个符号、一个列表和一个数字所组成的列表
(defun hello-world ()
(format t "hello, world"))
; 由两个符号、空列表和一个列表——其本身又含两个符号和一个字符串所组成的列表
函数调用:
(function-name argument*)
特殊操作符:
(1)IF:
(if test-form then-form [else-form])
其中test-form是条件,then-form是条件值为真时求值的表达式,else-form是条件值为假时求值的表达式。
(2)QUOTE:
(quote statement)
它接受一个单一的表达式作为其“参数”并简单地返回它,不经求值。
如:(quote (+ 1 2)) → (+ 1 2)
由于 QUOTE 被用得十分普遍,以至于读取器中内置了一个它的特殊的语法形式:
(quote (+ 1 2)) 等价于 '(+ 1 2)
(3)LET:
(let statement)
它用来创建新的变量绑定。
如:(let ((x 10)) x)
求值得到10,因为在第二个x的求值环境中,它是由 LET 赋值为10的变量名。
真、假和等价:
符号 NIL 是唯一的假值,其他所有的都是真值。
符号 T 是标准的真值,可用于需要返回一个非 NIL 值却又没有其他值可用的情况。
关于 NIL 它是唯一一个既是原子(atom)又是列表(list)的对象,除了用来表示假以外,它还用来表示空列表。
因为 NIL 是一个以符号 NIL 作为其值的常值变量名,所以表达式 nil、()、'nil 以及'()求值结果是相同的
基于同样的道理 t 和 't 的求值结果也完全相同:符号 T 。
四个“通用”等价谓词:
EQ:
EQ 用来测试“对象标识”,只有当两个对象相同时才是 EQ 等价的。不幸的是,数字和字符的对象标识取决于这些数据类型在特定Lisp平台上的实现的方式。因此带有相同值的两个数字或字符可能会被 EQ 认为是等价的也可能会是不等价的。
EQL:
EQL 和 EQ 有相似的行为,除此之外,它也可以保证当相同类型的两个对象表示相同的数字或字符时,它们是等价的。
因此 (eql 1 1) 能确保值为真,而 (eql 1 1.0)则被确保是假,因为整数 1 和浮点数 1.0 是不同类型的对象。
EQUAL:
EQUAL 相比 EQL 的宽松之处在于,它将在递归上具有相同结构和内容的列表视为等价。
EQUAL 也认为含有相同字符的字符串是等价的。
它对于位向量和路径名也定义了比 EQL 更加宽松的等价性。
EQUALP:
更加宽松,并且在考察两个含有相同字符的字符串的等价性时忽略了大小写的区别。
它还认为如果两个字符只是大小写上有区别,那么它们就是等价的。
只要数字表示相同数学意义上的值,它们也是等价的。
因此 (equalp 1 1.0) 值为真。
函数:
函数定义:
(defun name (parameter*)
"Optional documentation string."
boby-form*)
任何符号都可用作函数名。通常函数名仅包含字典符和连字符,但是在特定的命名约定里,其他字符也允许使用。
如果一个字符串紧跟在形参列表之后,那么它应该是一个用来描述函数用途的文档字符串。当定义函数时,该文档字符串将被关联到函数名上,并且以后可以通过 DOCUMENTATION 函数来获取。
例如:(documentation 'foo 'function) 将返回 foo 的文档字符串。
一个 DEFUN 的主体可由任意数量的 Lisp 表达式所构成,它们将在函数被调用时依次求值,而最后一个表达式的值将被作为整个函数的值返回。另外 RETURN-FROM 特殊操作符可用于从函数的任何位置立即返回。
一个复杂的函数示例:
(defun verbose-sum (x y)
"Sum any two numbers after printing a message."
(format t "Summing ~d and ~d.~%" x y)
(+ x y))
可选形参:
为了定义一个带有可选形参的函数,在必要形参的名字之后放置符号 &optional ,后接可选形参的名字。
如:(defun foo (a b &optional c d) (list a b c d))
调用结果:
(foo 1 2) → (1 2 NIL NIL)
(foo 1 2 3) → (1 2 3 NIL)
(foo 1 2 3 4) → (1 2 3 4)
如果想用一个特定的值来指定为默认值,则可以用一个表达式来实现:
(defun foo (a &optional (b 10)) (list a b))
调用结果:
(foo 1 2) → (1 2)
(foo 1) → (1 10)
如果需要更灵活地选择默认值。比如可能想要基于其他形参来计算默认值。默认值表达式可以引用早先出现在形参列表中的形参。如果要编写一个返回矩形的某种表示的函数,并且想要使它可以特别方便地产生正方形,那么可以使用如下形式:
(defun make-rectangle (width &optional (height width)) ...)
有时,有必要去了解一个可选形参的值究竟是被调用者明确指定还是使用了默认值。可以通过在形参标识符的默认值表达式之后添加另一个变量名来做到,该变量将在调用者实际为该形参提供了一个实参时绑定到真值,否则为 NIL 。通常约定,这种变量的名字与对应的真实形参相同,但带有一个 -supplied-p 后缀,如:
(defun foo (a b &optional (c 3 c-supplied-p))
(list a b c c-supplied-p))
调用结果:
(foo 1 2) → (1 2 3 NIL)
(foo 1 2 3) → (1 2 3 T)
(foo 1 2 4) → (1 2 4 T)
剩余形参:
Lisp 允许在符号 &rest 之后包括一揽子形参。如果函数带有 &rest 形参,那么任何满足了必要和可选形参之后的其余所有实参就将被收集到一个列表里成为该 &rest 形参的值。 这样,FORMAT 和 + 的形参列表可能看起来会是这样:
(defun format (stream string &rest values) ...)
(defun + (&rest numbers) ...)
关键字形参:
它允许调用者指定具体形参相应所使用的值。
为了使函数带有关键字形参,在任何必要的 &optional 和 &rest 形参之后,可以加上符号 &key 以及任意数量的关键字形参标识符,后者的格式类似于可选形参标识符。
如:(只有关键字形参的函数)
(defun foo (&key a b c) (list a b c))
如果一个给定的关键字没有出现在实参列表中,那么对应的形参将被赋予其默认值,如同可选形参那样。
调用结果:
(foo) → (NIL NIL NIL)
(foo :a 1) → (1 NIL NIL)
(foo :b 1) → (NIL 1 NIL)
(foo :c 1) → (NIL NIL 1)
(foo :a 1 :c 3) → (1 NIL 3)
(foo :a 1 :b 2 :c 3) → (1 2 3)
(foo :c 3 :a 1 :b 2) → (1 2 3)
如同可选形参一样,关键字形参也可以提供一个默认值形式以及一个 -supplied-p 变量名。这个默认值都可以引用早先出现在形参列表中的形参。
(defun foo (&key (a 0) (b 0 b-supplied-p) (c (+ a b)))
(list a b c b-supplied-p))
调用结果:
(foo :a 1) → (1 0 1 NIL)
(foo :b 1) → (0 1 1 T)
(foo :b 1 :c 4) → (0 1 4 T)
(foo :a 2 :b 1 :c 4) → (2 1 4 T)
如果想要让调用者用来指定形参的关键字不同于实际形参名,可以将形参名替换成一个列表,令其含有调用函数时使用的关键字以及用作形参的名字。
(defun foo (&key ((:apple a)) ((:box b) 0) ((:charlie c) 0 c-supplied-p))
(list a b c c-supplied-p))
调用结果:
(foo :apple 10 :box 20 :charlie 30) → (10 20 30 T)
混合不同的形参类型:
当用到多种类型的形参时,它们必须以这样的顺序声明:
(1)必要形参
(2)可选形参
(3)剩余形参
(4)关键字形参
一般不会将 &optional 或者 &rest 和 &key 形参组合使用。
函数返回值:
RETURN-FROM 特殊操作符,它能立即以任何值从函数中间返回。
它还可以从一个由 BLOCK 特殊操作符所定义的代码块中返回。
(defun foo (n)
(dotimes (i 10)
(dotimes (j 10)
(when (> (* i j) n)
(return-from foo (list i j))))))
高阶函数——作为数据的函数:
特殊操作符 FUNCTION 提供了用来获取一个函数对象的方法。它接受单一实参并返回与该参数同名的函数。这个名字是不被引用的。因此如果一个函数 foo 的定义如下:
CL-USER> (defun foo (x) (* 2 x))
FOO
CL-USER> (function foo)
#<Interpreted Function FOO>
如同 (') 是 QUOTE 的语法糖一样。(#') 是 FUNCTION 的语法糖。因此:
CL-USER> #'foo
#<Interpreted Function FOO>
Common Lisp 提供了两个函数用来通过函数对象调用函数: FUNCALL 和 APPLY。
它们的区别仅在于如何获取传递给函数的实参。
FUNCALL:
用于在编写代码时确切知道传递给函数多少实参时。FUNCALL 的第一个实参是被调用的函数对象,其余的实参被传递到该函数中。
(foo 1 2 3) ≡ (funcall #'foo 1 2 3)
下面这个函数演示了 FUNCALL 的另一个更有建设性的用法。它接受一个函数对象作为实参,并使用实参函数在 min 和 max 之间以 step 为步长的返回值来绘制一个简单的ASCII式柱状图:
(defun plot (fn min max step)
(loop for i from min to max by step do
(loop repeat (funcall fn i) do (format t "*"))
(format t "~%")))
APPLY:
和 FUNCALL 一样,APPLY 的第一个参数是一个函数对象。但在这个函数对象之后,它期待一个列表而非单独的实参。它将函数应用在列表的值上:
(apply #'plot plot-data) ; 假如该函数需要的参数以列表的形式存在 plot-data 中
更方便的是,APPLY 还接受“孤立”(loose)的实参,只要最后一个参数是一个列表就行:
(apply #'plot #'exp plot-data)
匿名函数:
(lambda (parameters) body)
可以这么用:
(funcall #'(lambda (x y) (+ x y)) 2 3) → 5
还可以这么用
((lambda (x y) (+ x y)) 2 3) → 5 ; 几乎没人这么做
示例:
CL-USER> (plot #'(lambda (x) (* 2 x)) 0 10 1)
LAMBDA 表达式的另一项重要用途是制作闭包 (closure),即捕捉了其创建时环境信息的函数。
(defun make-cd (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped ripped))
(defvar *db* nil)
(defun add-record (cd) (push cd *db*))
(defun dump-db ()
(dolist (cd *db*)
(format t "~{~a: ~10t~a~%~}~%" cd)))
(defun prompt-read (prompt)
(format *query-io* "~a: " prompt)
(force-output *query-io*)
(read-line *query-io*))
(defun prompt-for-cd ()
(make-cd
(prompt-read "Title")
(prompt-read "Artist")
(or (parse-integer (prompt-read "Rating") :junk-allowed t) 0)
(y-or-n-p "Ripped [y/n]:")))
(defun add-cds()
(loop (add-record (prompt-for-cd))
(if (not (y-or-n-p "Another? [y/n]:")) (return))))
(defun save-db (filename)
(with-open-file (out filename
:direction :output
:if-exists :supersede)
(with-standard-io-syntax
(print *db* out))))
(defun load-db (filename)
(with-open-file (in filename)
(with-standard-io-syntax
(setf *db* (read in)))))
(defun select-by-artist (artist)
(remove-if-not
#'(lambda (cd) (equal (getf cd :artist) artist))
*db*))
(defun select (selector-fn)
(remove-if-not selector-fn *db*))
(defun artist-selector (artist)
#'(lambda (cd) (equal (getf cd :artist) artist)))
(defun update (selector-fn &key title artist rating (ripped nil ripped-p))
(setf *db*
(mapcar
#'(lambda (row)
(when (funcall selector-fn row)
(if title (setf (getf row :title) title))
(if artist (setf (getf row :artist) artist))
(if rating (setf (getf row :rating) rating))
(if ripped-p (setf (getf row :ripped) ripped)))
row) *db*)))
(defun delete-rows (selector-fn)
(setf *db* (remove-if selector-fn *db*)))
(defun make-comparison-expr (field value)
`(equal (getf cd ,field) ,value))
(defun make-comparisons-list (fields)
(loop while fields
collecting (make-comparison-expr (pop fields) (pop fields))))
(defmacro where (&rest clauses)
`#'(lambda (cd) (and ,@(make-comparisons-list clauses))))