Lisp-02: 函數
- 2020 年 4 月 9 日
- 筆記
函數(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