lexical-let是怎么模拟闭包的?

(lexical-let ((x 1))
  (lambda (x y)
    (+ x y)))

扩展出来的结果是

(lambda
  (&rest --cl-rest--)
  (apply
   '(lambda
      (#:G535 x y)
      (+
       (symbol-value #:G535)
       y))
   '#:--x-- --cl-rest--))

谁能帮忙解释一下这个结果的意思?完全看不懂阿

感觉就是把原来 x 的位置换成一个类似于全局变量的东西。

简单的说这个根编译器有关。跟first order logic里scope的概念一个意思。

#:--x-- 绑定的是未扩展前的表达式中的 x 的值 1。生成的表达式中的 x 没有作用。另外,#:--x-- 应该是个 uninterned symbol,你用的 Common Lisp ?

elisp也有uninterned symbol啊. 把print-gensym设置为t就会输出这种格式了.

我觉得比较奇怪的是

  1. 看起来内部apply的lambda变成了三个参数?
  2. 传递给#:G535的值到底是什么?为什么symbol-value来取值呢?

就是把 (x 1) 放到全局作用域,所以需要一个唯一的“索引”。

在我这里:

(macroexpand
 '(lexical-let ((x 1))
    (lambda (x y)
      (+ x y))))

展开之后是这样的:

(let ((--cl-x-- (make-symbol "--x--")))
  (setf (symbol-value --cl-x--) 1)
  (list (quote lambda)
        (quote (&rest --cl-rest--))
        (list (quote apply)
              (list (quote quote)
                    (function (lambda (G42 x y)
                                (+ (symbol-value G42) y))))
              (list (quote quote) --cl-x--) ;; (x 1) 的那个 x, 对应 lambda 参数 G42
              (quote --cl-rest--))))        ;; lambda 其余的参数

我也不明白 lambda 的 x 参数的用意,最后不是没用到吗?

1 个赞

以前似乎有印象 CL 会在 print uninterned symbol 的时候前面加上 #:,并不知道 elisp 也这样。

因为 lexical scope,所以不管外面传什么 x,也是不影响的。为了模拟 lexical scope 的效果,展开时你需要隐式传入一个新的参数(即保存 lexical scope 里那个 x 的值的 symbol),原来函数接受 2 个参数,所以就变成 3 个了。然后 partially apply,又变回两个,外面看上去没有变化。

#:G535 是一个 uninterned symbol,看 @twlz0ne 贴的展开形式会更清楚,作用就是用来代替原来的 x symbol。既然 x 原来是个 symbol,所以也得传进来一个 symbol,取值就用 symbol-value 了。

1 个赞

这么看来的话,lexical-let的实现是有bug的。 就以这个例子来说,很显然(+ x y) 中的x是指代lambda中的参数x,而扩展后变成指向lexical-let中的x了。

可以试着去 http://emacs.stackexchange.com/ 去提问,的确 lexical-let 和开启 lexical-bindinglet 是不一样的。(也许本来就不一样?或者是 bug ?)