这是一个很好的问题,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))
工作,因为它隐式包装在a中#'
(尝试将lambda
表单扩展为宏你会看到它)。一个相关的 hack 可以很好地使用lambda
表达式作为函数应用程序的头部(这是您尝试过的事情之一)。
let
etc 绑定本地值,在某些情况下,您会想要绑定本地函数——为此,有新的绑定构造:flet
和labels
如果所有这些看起来很奇怪和/或过于复杂,那么你并不孤单。这是 Common Lisp 和 Scheme 之间的主要区别之一。(这种差异导致两种语言的常用习语发生变化:Scheme 代码往往比 Common Lisp 代码更频繁地使用高阶函数。像往常一样,对于这类宗教问题,有些人支持 CL 的做法,声称高阶函数令人困惑,因此他们喜欢显式的代码内提醒。)