首页 > 解决方案 > Lisp (Allegro Common Lisp) 如何在 lambda 中使用带有 ' vs #' 的变量

问题描述

我希望有人能解释为什么测试 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)))

我正在使用 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]

标签: lispcommon-lispquoting

解决方案


首先,您应该知道您的测试 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))

这只是使用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)))

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

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

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

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

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

> (test-7 1 (coerce '(lambda (x) (expt x y)) 'function))

很明显,这在词法范围的语言中是行不通的。

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

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

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

(let ((x 1))
  (function (lambda (y) (+ x y))))

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

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

y创建的函数所引用的绑定是y词法可见的绑定,也就是test-4自身的参数。


推荐阅读