这是一个好问题,也是Common Lisp比较令人困惑的地方。由于历史原因,Common Lisp有两个命名空间——一个用于函数,另一个用于值。为了实现这一点,对于函数应用的头位置和其余部分有两种不同的求值规则——第一种将符号作为函数名进行求值,第二种将符号作为变量引用进行求值。显然,在某些情况下,该值实际上是一个函数——例如,如果您编写一个
mapcar
函数,您需要做一些类似的事情。
(defun my-mapcar (f l)
(if (null l)
'()
(cons (f (car l)) (my-mapcar f (cdr l)))))
但是这样做行不通 - 它会抱怨
f
是一个未知的函数。对于这些情况,有一个特殊的函数叫做
funcall
,它接受一个函数和该函数的参数,并像往常一样应用该函数 - 由于
funcall
是一个普通函数,其所有参数都按照通常方式计算(作为值)。因此,上述问题可以通过使用它来解决:
(defun my-mapcar (f l)
(if (null l)
'()
(cons (funcall f (car l)) (my-mapcar f (cdr l)))))
正如您现在可能怀疑的那样,有镜像情况--您想将某些内容作为函数而不是值进行评估。例如,以下内容无法正常工作:
(my-mapcar 1+ '(1 2 3))
因为它引用的是1+
变量而不是函数。对于这些情况,有一种特殊形式叫做function
,它将其内容作为函数进行评估并将其作为值返回:
(my-mapcar (function 1+) '(1 2 3))
而且它可以使用#'
进行缩写:
(my-mapcar #'1+ '(1 2 3))
这个故事还没有结束——举几个例子:
在某些情况下,一个简单的引用名称可以作为函数使用——例如,最后一个示例中的'1+
可行——但这是一种只能查看全局绑定名称的hack,因此#'
几乎总是更好的选择。
类似的hack可以与lambda
一起使用——因此,您可以使用(my-mapcar '(lambda (x) (1+ x)) '(1 2 3))
,实际上,您可以使用(list 'lambda '(x) '(1+ x))
,但这甚至更糟糕(并且IIRC,不可移植),但使用(lambda (x) (1+ x))
可以工作,因为它隐式包装在#'
中(尝试将lambda
表单扩展为宏,你会看到它)。 相关的hack使得可以将lambda
表达式用作函数应用的头部(这是你尝试过的事情之一)。
let
等绑定局部值,在某些情况下,您将希望绑定局部函数——为此,有新的绑定构造: flet
和labels
如果所有这些看起来都很奇怪和/或过于复杂,那么你并不孤单。 这是Common Lisp和Scheme之间的主要区别之一。 (然后差异导致两种语言中常见习惯用法的变化:Scheme代码倾向于更频繁地使用高阶函数,而Common Lisp代码则相反。像这样的信仰问题通常存在争议,有些人支持CL所做的事情,声称高阶函数很困惑,因此他们喜欢明确的代码提醒。)