循环宏和闭包的意外行为

时间:2013-03-30 03:12:34

标签: common-lisp

为什么这些表单会这样表现?

CL-USER>
(setf *closures*
      (loop for num in (list 1 2 3 4)
            collect (lambda ()
                      num)))
(     
#<COMPILED-LEXICAL-CLOSURE #x302004932E1F>
#<COMPILED-LEXICAL-CLOSURE #x302004932DCF>
#<COMPILED-LEXICAL-CLOSURE #x302004932D7F>
#<COMPILED-LEXICAL-CLOSURE #x302004932D2F>)
CL-USER> 
(funcall (first *closures*))
4
CL-USER> 
(funcall (second *closures*))
4

我原本期望第一个funcall返回1,第二个返回2,等等。这个行为与Clozure Common Lisp和Steel-Bank Common Lisp实现一致。

如果我将循环宏重写为使用dolist的版本,我期望的是返回的内容:

(setf *closures*
      (let ((out))
        (dolist (item (list 1 2 3 4) (reverse out))
          (push (lambda () item) out))))
(
#<COMPILED-LEXICAL-CLOSURE #x302004A12C4F>
#<COMPILED-LEXICAL-CLOSURE #x302004A12BFF>  
#<COMPILED-LEXICAL-CLOSURE #x302004A12BAF>
#<COMPILED-LEXICAL-CLOSURE #x302004A12B5F>)
CL-USER> 
(funcall (first *closures*))
1
CL-USER> 
(funcall (second *closures*))
2

CL-使用者&gt;

循环宏版本发生了什么?

2 个答案:

答案 0 :(得分:8)

num是所有lambdas共享的相同变量。

使用

(setf *closures*
  (loop for num in (list 1 2 3 4)
        collect (let ((num1 num))
                  (lambda ()
                    num1))))

num1是每次迭代的新变量。

dolist开始,“它是依赖于实现的,是否dolist在每次迭代时建立var的新绑定,或者它是否在开始时为var建立一次绑定,然后在任何后续迭代中分配它。” (CLHS,Macro DOLIST)。所以它可能适用于一个实现,而另一个实现则失败。

答案 1 :(得分:4)

在评估LOOP期间,名称num表示相同的绑定。 也许你想写:

(mapcar 'constantly (list 1 2 3 4))

得到你的意思。