7

我正在阅读 Peter Norvig 的人工智能编程范式,我遇到了一个我自己无法解决的问题(这是我对 Lisp 的介绍)。这个问题确实很小,但显然不是我的小脑袋能解决的。

为什么当函数的值为 lambda 时,将该函数用作列表的第一个元素是错误的。例如:

语言:

(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)

我希望这是有道理的!

4

4 回答 4

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,所以#'几乎总是更好

  • 可以使用类似的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表达式作为函数应用程序的头部(这是您尝试过的事情之一)。

  • letetc 绑定本地值,在某些情况下,您会想要绑定本地函数——为此,有新的绑定构造:fletlabels

如果所有这些看起来很奇怪和/或过于复杂,那么你并不孤单。这是 Common Lisp 和 Scheme 之间的主要区别之一。(这种差异导致两种语言的常用习语发生变化:Scheme 代码往往比 Common Lisp 代码更频繁地使用高阶函数。像往常一样,对于这类宗教问题,有些人支持 CL 的做法,声称高阶函数令人困惑,因此他们喜欢显式的代码内提醒。)

于 2011-05-01T19:33:12.813 回答
4

((lambda (x) ...) ...)是评估器规则中的硬编码特例。它不评估表单中的第一个元素并将结果用作一般情况。您必须使用funcallapply调用作为评估其他形式的结果的函数是正常的。

于 2011-05-01T19:21:32.757 回答
2

Common Lisp 不允许返回 lambda 的函数作为表达式中的第一项的原因与Lisp-1 和 Lisp-2之间的区别有关。

如果 Common Lisp 允许((some-func) 1)等价于(funcall (some-func) 1),那么不允许 say(let ((f #'some-func)) (f 1))而不是 require可能会被认为是不一致的(funcall f 1)

于 2011-05-01T19:59:49.947 回答
1

不支持这种形式实际上有一个很好的理由:它们是模棱两可的。考虑在 Common Lisp 中定义函数名的方式:

函数名 n。1.(在一个环境中)一个符号或一个列表 (setf symbol),它是那个环境中一个函数的名称。2.一个符号或一个列表 (setf symbol)

鉴于此,像下面这样的表单应该如何表现?

((setf car) 10 x)

应该将这个 setx的 car 设置为 value 10,还是应该执行表单(setf car)(这将是一个错误)并尝试使用它的返回值作为函数来调用参数10x?Common Lisp 没有指定任何行为,我完全不清楚这是一个糟糕的选择。毕竟,据我所知,标准中的任何内容都不会阻止符合标准的实现扩展有效形式的定义以支持更广泛的运算符名称(因此特殊大小写的 setf 函数在这里也没有真正的帮助)。

于 2011-05-02T17:45:07.840 回答