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