Common Lisp 参数传递的几种形式
1. 常规的命名参数:
(defun f (a b) (+ a b)) (f 1 2)
输出:
3
2. 可选参数:
(defun f (a b &optional c d) (list a b c d)) (f 1 2 3)
输出:
(1 2 3 NIL)
未显示提供的参数,会以 NIL 值代替。
可选参数可以指定默认值:
(defun f (a b &optional (c "Hello") d) (list a b c d)) (f 1 2)
输出:
(1 2 "Hello" NIL)
可选的默认值也可以通过引用前面的参数值来计算:
(defun make-rectangle (width &optional (height width)) (list width height)) (make-rectangle 3 4)
输出:
(3 4)
(make-rectangle 3)
输出:
(3 3)
如果需要判断调用方是否提供了某个可选参数,则可以修改参数列表,在默认值后面添加一个变量名,其值为 t 的时候表示提供了该参数,否则为 NIL.
(defun f (a &optional (b 3 b-supplied-p)) (list a b b-supplied-p))
输入:
(f 1)
输出:
(1 3 NIL)
输入:
(f 1 2)
输出:
(1 2 T)
3. rest 参数
如果除了命名参数和可选参数之外,还有更多传递的参数值,它们可以被收集到 &rest 符号后面指定的一个列表变量中。
比如,format 和 + 函数的形式可以定义为:
(defun format (stream string &rest values) …) (defun + (&rest numbers) …)
例如,可以重定义一个 + 函数的形式:
(defun add (&rest values) (apply '+ values))
输入:
(add 1 2)
输出:
3
输入:
(add)
输出:
0
4. 关键字参数
关键字参数可以用来传递参数列表中任意一部分参数,而不管其所在的位置如何。
可以在命名参数、可选参数,和 rest 参数后面,添加 &key 符号,然后接着指定任意数目的关键字参数。
例子:
(defun f (a &optional b &rest values &key d (e 10) (f 11 f-supplied-p)) (list a b values d e f)) COMMON-LISP-USER> (f 1) (1 NIL NIL NIL 10 11) COMMON-LISP-USER> (f 1 2 :d 99 :e 100) (1 2 (:D 99 :E 100) 99 100 11)
另外,还可以给关键字参数指定不同的调用名称和内部名称。这样可以提供比较有意义的 public API 的参数名,而内部实现使用简短的名称。但这个功能不是很常用。
COMMON-LISP-USER> (defun foo (&key ((:apple a)) ((:box b)) ((:charlie c) 0 c-supplied-p)) (list a b c c-supplied-p)) FOO COMMON-LISP-USER> (foo :apple 10 :box 20 :charlie 30) (10 20 30 T)
5. Body 参数
用 &body 符号指定。和 &rest 大致相同,但是具体的 Common Lisp 实现可以扩展其特定的行为。
&body 有 pretty-printing 效果。
6. 组合使用各种形式的参数
如果一个函数需要用到所有类型的参数,那么他们的出现次序必须为:
命名参数,可选参数,rest 参数,关键字参数。
如果用到多种类型的参数,最好是把它们进行合适当的合并,或者简化成全部使用关键字参数。否则,可能会出现参数被“吞掉”的情况,从而导致不正确的行为。