Lisp (Allegro Common Lisp) 如何使用 lambda 中的变量与 ' vs #'

j_v*_*w_d 2 lisp common-lisp quoting

我希望有人能解释为什么测试 1-5 有效但测试 6 没有。我认为用 ' 引用 lambda 并在 lambda 前面使用 #' 都返回指向该函数的指针,唯一的区别是 #' 将首先编译它。

(defun test-1 (y)
  (mapcar (lambda (x) (expt x 2))
      '(1 2 3)))

(defun test-2 (y)
  (mapcar (lambda (x) (expt x y))
      '(1 2 3)))

(defun test-3 (y)
  (mapcar #'(lambda (x) (expt x 2))
      '(1 2 3)))

(defun test-4 (y)
  (mapcar #'(lambda (x) (expt x y))
      '(1 2 3)))

(defun test-5 (y)
  (mapcar '(lambda (x) (expt x 2))
      '(1 2 3)))

(defun test-6 (y)
  (mapcar '(lambda (x) (expt x y))
      '(1 2 3)))
Run Code Online (Sandbox Code Playgroud)

我正在使用 Franz Industries Allegro Common Lisp 的免费版本。以下是输出:

(test-1 2)     ; --> (1 4 9)
(test-2 2)     ; --> (1 4 9)
(test-3 2)     ; --> (1 4 9)
(test-4 2)     ; --> (1 4 9)
(test-5 2)     ; --> (1 4 9)
(test-6 2)     ; --> Error: Attempt to take the value of the unbound variable `Y'. [condition type: UNBOUND-VARIABLE]
Run Code Online (Sandbox Code Playgroud)

小智 6

首先,您应该知道您的测试 1-4 符合 Common Lisp,而您的测试 5 和 6 不符合。我相信 Allegro 完全可以为 5 和 6 做它所做的事情,但它所做的却超出了标准。谈论这一点的标准的一点是函数的定义mapcar,它以函数指示符作为参数,以及函数指示符的定义:

功能指示符 n . 一个指示器用于功能; 也就是说,一个表示函数的对象,它是以下之一:符号(表示在全局环境中由该符号命名的函数)或函数(表示自身)。如果一个符号用作函数指示符,但它没有作为函数的全局定义,或者它具有作为宏或特殊形式的全局定义,则结果是未定义的。[...]

由此很明显,一个列表 like(lambda (...) ...)不是一个函数指示符:它只是一个列表,其 car 恰好是lambda。Allegro 正在做的是注意到这个列表实际上可以变成一个函数并这样做。

好吧,让我们编写一个版本,mapcar它的功能与 Allegro 的功能相同:

(defun mapcar/coercing (maybe-f &rest lists)
  (apply #'mapcar (coerce maybe-f 'function) lists))
Run Code Online (Sandbox Code Playgroud)

这只是使用coercewhich 是一个知道如何将这样的列表转换为函数的函数,等等。如果它的参数已经是一个函数,coerce就返回它。

现在我们可以使用这个函数编写两个测试:

(defun test-5/coercing (y)
  (mapcar/coercing '(lambda (x) (expt x 2))
                   '(1 2 3)))

(defun test-6/coercing (y)
  (mapcar/coercing '(lambda (x) (expt x y))
                   '(1 2 3)))
Run Code Online (Sandbox Code Playgroud)

那么,在序言之后,为什么不能test-6/explicit工作?答案是 Common Lisp 是(除了特殊变量)词法范围的词法作用域只是一种奇特的说法,即可用的绑定(变量)正是您通过查看程序源可以看到的绑定(变量)。(除了,对于特殊绑定的 CL,我将忽略它,因为这里没有。)

因此,考虑到这一点,请考虑test-6/coercing,特别是在该调用中对mapcar/coercing: 的调用,coerce必须将列表(lambda (x) (expt z y))转换为函数。所以它这样做。但是它返回的函数没有绑定,y并且其中没有y可见的绑定:该函数使用y“free”。

唯一可行的方法是,如果coerce为我们构造的函数动态查找y. 嗯,这就是动态范围语言所做的,但 CL 不是动态范围的。

也许让这个更清楚的一种方法是意识到我们可以直接从函数中提取函数的创建:

(defun test-7 (y f)
  (mapcar f '(1 2 3)))

> (test-7 1 (coerce '(lambda (x) (expt x y)) 'function))
Run Code Online (Sandbox Code Playgroud)

很明显,这不能在词法范围的语言中工作。

那么,测试 1-4 是如何工作的呢?

嗯,首先这里实际上只有两个测试。在 CL 中,lambda是一个宏,(lambda (...) ...)完全等同于(function (lambda (...) ...)). 当然#'(lambda (...) ...)一样(function (lambda (...) ...)):它只是一个读宏它。

并且(function ...)是一个神奇的东西(一种特殊形式),它说“这是一个函数”。重要的function是它不是一个函数:它是一个非常神奇的东西,它告诉评估器(或编译器)它的参数是当前词法上下文中函数的描述,因此,例如在

(let ((x 1))
  (function (lambda (y) (+ x y))))
Run Code Online (Sandbox Code Playgroud)

x此创建的函数所引用的 是 的x边界let。因此,在您的测试 2 和 4(相同)中:

(defun test-4 (y)
  (mapcar (function (lambda (x) (expt x y)))
      '(1 2 3)))
Run Code Online (Sandbox Code Playgroud)

所述的结合y,其产生的功能是指被结合的y是词法可见,它是参数test-4本身。