Lisp-02: 函数
函数(functions)
在 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) NIL CL-USER> (fboundp 'foo) NIL ;; We create a variable: CL-USER> (defparameter foo 42) FOO * foo 42 ;; 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