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)) ...)  

    这样,调用 hellohappy 的值默认就是 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 支持返回多个值。

返回多个值有三个关键词:valuesmultiple-value-bindnth-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-listmultiple-value-list 相反,它返回的是列表中的每个元素

    (values-list '(1 2 3))  ;; 1  ;; 2  ;; 3  

匿名函数 lambda

匿名函数的声明如下:

(lambda (x) (print x))  

匿名函数的调用:

((lambda (x) (print x)) "hello")  ;; hello  
  • funcallapply

    (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 中提供了两个函数来查看变量或函数是否赋值/绑定:boundpfboundp

    ;; 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