我正在阅读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)
我希望这是有道理的!
答案 0 :(得分:6)
这是一个很好的问题,而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,非-portable),但使用(lambda (x) (1+ x))
有效,因为它隐含地包含在#'
中(尝试将lambda
格式扩展为宏,您会看到它)。相关的黑客可以使用lambda
表达式作为函数应用程序的头部(这是你尝试过的东西之一)。
let
等绑定本地值,在某些情况下,您需要绑定本地函数 - 为此,有新的绑定结构:flet
和{{1} }
如果所有这些看起来都很奇怪和/或过于复杂,那么你并不孤单。这是Common Lisp和Scheme之间的主要区别之一。 (差异导致两种语言中常见习语的变化:方案代码往往比Common Lisp代码更频繁地使用更高阶函数。像往常一样,这些宗教问题,有些人主张CL做什么,声称高阶函数令人困惑,所以他们喜欢显式的代码内提醒。)
答案 1 :(得分:4)
((lambda (x) ...) ...)
是评估者规则中的硬编码特殊情况。它不是在评估表单中的第一个元素,而是将结果用作一般情况。您必须使用funcall
或apply
来调用作为评估其他表单的结果的函数,这是正常的。
答案 2 :(得分:2)
Common Lisp不允许将lambda作为表达式中第一项的函数的原因与Lisp-1 and Lisp-2之间的区别有关。
如果Common Lisp允许((some-func) 1)
等同于(funcall (some-func) 1)
,那么可能会认为它不一致而不允许说(let ((f #'some-func)) (f 1))
而不是(funcall f 1)
。
答案 3 :(得分:1)
实际上有一个很好的理由不支持这种形式:它们含糊不清。考虑在Common Lisp中定义function names的方式:
功能名称 n 。 1.(在环境中)符号或列表
(setf symbol)
,即名称 环境中的功能。 2. 符号或列表(setf symbol)
。
鉴于此,下面的表格应如何表现?
((setf car) 10 x)
这应该将x
的汽车设置为值10
,还是应该执行(setf car)
形式(这将是一个错误)并尝试将其返回值用作使用参数10
和x
调用的函数? Common Lisp既没有指定行为,也没有告诉我这是一个糟糕的选择。毕竟,据我所知,标准中没有任何内容阻止符合要求的实现扩展有效表单的定义以支持更广泛的运算符名称(因此特殊套管setf函数在这里也没有真正帮助)。