我正在阅读Peter Norvig的人工智能编程范例,我遇到了一个我无法自己解决的问题(这是我对Lisp的介绍).真的,问题是相当小的问题,但显然不是我的小脑能解决的问题.
为什么当函数的值是lambda时,将该函数用作列表的第一个元素是错误的.例如:
Lisp的:
(defun some-func () #'(lambda (x) x)) ;; At REPL ;; Does not work > ((some-func) 1) ;; Does work > ((lambda (x) x) 1) ;; Also works > (funcall (some-func) 1)
我希望这是有道理的!
这是一个很好的问题,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,因此#'
几乎总是更好
一个类似的黑客可以使用lambda
- 所以你可以使用(my-mapcar '(lambda (x) (1+ x)) '(1 2 3))
,事实上,你可以使用(list 'lambda '(x) '(1+ x))
哪个更糟糕(和IIRC,非可移植),但使用(lambda (x) (1+ x))
工作,因为它隐含地包装在一个#'
(尝试将lambda
表单扩展为宏你会看到它.一个相关的hack可以将lambda
表达式用作函数应用程序的头部(这是你尝试过的东西之一).
let
etc绑定本地值,在某些情况下你会想要绑定本地函数 - 为此,有新的绑定结构:flet
和labels
如果所有这些看起来都很奇怪和/或过于复杂,那么你并不孤单.这是Common Lisp和Scheme之间的主要区别之一.(差异导致两种语言中常见习语的变化:方案代码往往比Common Lisp代码更频繁地使用更高阶函数.像往常一样,这些宗教问题,有些人主张CL做什么,声称高阶函数令人困惑,所以他们喜欢显式的代码内提醒.)