在 Lisp 中,函数分两种:有名函数和匿名函数(lambda函数)。
有名函数 defun
有名函数的标准定义格式为:
(defun <name> (list of arguments)"docstring"(function body))
在函数中,返回值是函数主体中的最后一个表达式的结果。与大部分语言不同的是,lisp 中的函数没有 “return xx” 这样的语句用来声明返回值。例如:
(defun hello-world ();;(print "hello world!"))
调用 hello-world 函数:
(hello-world);; "hello world!" <-- output;; "hello world!" <-- a string is returned.
参数
必需参数
与大部分语言相同,函数中需要定义一些必需参数:
(defun hello (name)"Say hello to `name'."(format t "hello ~a !~&" name));; HELLO
调用 hello
(hello "me");; hello me ! <-- this is printed by `format`;; NIL <-- return value: `format t` prints a string to standard output and returns nil.
可选参数:&optional
可选参数定义在 &optional 这个关键词后面,且这些参数是有序的。如:
(defun hello (name &optional age gender) ...)
调用时需要这样调用:
(hello "me") ;; a value for the required argument, zero optional arguments(hello "me" "7") ;; a value for age(hello "me" 7 :h) ;; a value for age and gender
关键词参数:&key
通常情况下,要记住函数中参数的顺序很不方便,所以就引入了关键词参数 &key <name>。通过 :name <value> 这样的方式来传递参数。如果关键词 name 的值没有设置的话,默认为 nil。
(defun hello (name &key happy)"If `happy' is `t', print a smiley"(format t "hello ~a " name)(when happy(format t ":)~&"))
所以,我们可以这样调用 hello
(hello "me")(hello "me" :happy t)(hello "me" :happy nil) ;; useless, equivalent to (hello "me")
但是 (hello "me" :happy) 是非法的。
注:在关键词参数中,如果调用其他的关键词时,会报错,这是可以通过 &allow-other-keys 来修复。
(defun hello (name &key happy)(format t "hello ~a~&" name))(hello "me" :lisper t);; => Error: unknow keyword argument
(defun hello (name &key happy &allow-other-keys)(format t "hello ~a~&" name))(hello "me" :lisper t);; hello me
默认参数
有时,需要将函数的某个参数设置一个默认值,这样 hello 就可以这样定义
(defun hello (name &key (happy t)) ...)
这样,调用 hello 时 happy 的值默认就是 t 了。
不定参数:&rest
在不确定参数的个数时,可以使用 &rest <variable> 这样的方式来定义,其中 &rest 后面的参数会当作一个 list 来处理。
(defun mean (x &rest numbers)(/ (apply #'+ x numbers)(1+ (length numbers))))
(mean 1)(mean 1 2)(mean 1 2 3 4 5)
返回值
在 Lisp 中,函数的返回值就是函数主体中最后一个表达式执行的结果。也有非标准的 return-from <function name> <value> 这样的语句,但是大部分情况下用不到。同时,Common Lisp 支持返回多个值。
返回多个值有三个关键词:values,multiple-value-bind 和 nth-value
返回多个值不是将所有的结果都放入一个元组或列表中,这是很常见的概念混淆。
values
(defun foo (a b c)a)(foo :a :b :c);; :A(defvar *res* (foo :a :b :c));; :A
(defun foo (a b c)(values a b c))(foo :a :b :c);; :A;; :B;; :C(setf *res* (foo :a :b :c));; :A
从上面代码可以看出,如果 foo 返回的是一个列表的话,那么 res 的值将会是 (:a :b :c) 这样一个列表,而不是 :A 这个值。
multiple-value-list
该关键词的作用是将返回的多个值组合成一个列表
(multiple-value-list (values 1 2 3));; (1 2 3)
values-list
values-list 与 multiple-value-list 相反,它返回的是列表中的每个元素
(values-list '(1 2 3));; 1;; 2;; 3
匿名函数 lambda
匿名函数的声明如下:
(lambda (x) (print x))
匿名函数的调用:
((lambda (x) (print x)) "hello");; hello
funcall 和 apply
(funcall #'+ 1 2)(apply #'+ '(1 2))
返回函数的函数
(defun adder (n)(lambda (x) (+ x n)));; ADDER(adder 5);; #<CLOSURE (LAMBDA (X) :IN ADDER) {100994ACDB}>(funcall (adder 5) 3);; 8
上面示例中,(adder 5) 返回的是一个匿名函数。但是需要使用 funcall 关键词来调用,不能想正常函数调用来调用。
((adder 3) 5)In: (ADDER 3) 5((ADDER 3) 5)Error: Illegal
Common Lisp 中提供了两个函数来查看变量或函数是否赋值/绑定:boundp 和 fboundp
;; The symbol foo is bound to nothing:CL-USER> (boundp 'foo)NILCL-USER> (fboundp 'foo)NIL;; We create a variable:CL-USER> (defparameter foo 42)FOO* foo42;; Now foo is "bound":CL-USER> (boundp 'foo)T;; but still not as a function:CL-USER> (fboundp 'foo)NIL;; So let's define a function:CL-USER> (defun foo (x) (* x x))FOO;; Now the symbol foo is bound as a function too:CL-USER> (fboundp 'foo)T;; Get the function:CL-USER> (function foo)#<FUNCTION FOO>;; and the shorthand notation:* #'foo#<FUNCTION FOO>;; We call it:(funcall (function adder) 5)#<CLOSURE (LAMBDA (X) :IN ADDER) {100991761B}>;; and call the lambda:(funcall (funcall (function adder) 5) 3)8
注:在 Lisp 中,变量名和函数名可以相同,因为 Common Lisp 中变量和函数并不是存储在一起的,而是分开存储的。
闭包(Closure)
闭包,就是让一个函数可以使用一个 词法绑定(lexcial bindings)。On Lisp 中的定义为:函数和一组变量的绑定的组合(a combination of a function and a set of variable bindings)。 Let Over Lambda中对闭包的解读为:一个保存了词法的环境(a saved lexical environment)。 可以将闭包理解为 C 语言中的 结构体(struct)或者面向对象语言(Java/C++)中的 类(class)。
(let ((limit 3)(counter -1))(defun my-counter ()(if (< counter limit)(incf counter)(setf counter 0))))(my-counter)0(my-counter)1(my-counter)2(my-counter)3(my-counter)0
类似的
(defun repeater (n)(let ((counter -1))(lambda ()(if (< counter n)(incf counter)(setf counter 0)))))(defparameter *my-repeater* (repeater 3));; *MY-REPEATER*(funcall *my-repeater*)0(funcall *my-repeater*)1(funcall *my-repeater*)2(funcall *my-repeater*)3(funcall *my-repeater*)0
Currying
Concept
A related concept is that of currying which you might be familiar with if you’re coming from a functional language. After we’ve read the last section that’s rather easy to implement:
CL-USER> (defun curry (function &rest args)(lambda (&rest more-args)(apply function (append args more-args))))CURRYCL-USER> (funcall (curry #'+ 3) 5)8CL-USER> (funcall (curry #'+ 3) 6)9CL-USER> (setf (symbol-function 'power-of-ten) (curry #'expt 10))#<Interpreted Function "LAMBDA (FUNCTION &REST ARGS)" {482DB969}>CL-USER> (power-of-ten 3)1000
With the Alexandria library
Now that you know how to do it, you may appreciate using the
implementation of the
Alexandria
library (in Quicklisp).
(ql:quickload :alexandria)(defun adder (foo bar)"Add the two arguments."(+ foo bar))(defvar add-one (alexandria:curry #'adder 1) "Add 1 to the argument.")(funcall add-one 10) ;; => 11(setf (symbol-function 'add-one) add-one)(add-one 10) ;; => 11
Documentation
- functions: http://www.lispworks.com/documentation/HyperSpec/Body/t_fn.htm#function
- ordinary lambda lists: http://www.lispworks.com/documentation/HyperSpec/Body/03_da.htm
- multiple-value-bind: http://clhs.lisp.se/Body/m_multip.htm
