2013-03-30 34 views
4

爲什麼這些表單行爲如此?循環宏和閉包的意外行爲

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和鋼銀行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-USER>

這是怎麼回事用循環宏版本?

回答

8

num是所有lambda表達式共享的變量。

使用

(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

順便說一句,你可以命名'num1'爲'num';) (讓((NUM NUM))...) – monoid

+1

我以前用過這樣的:'(defmacro重新綁定(增值經銷商和身體的身體)\' (讓((mapcar#'list vars vars),@ body))' – lmj

+0

非常好的宏:) – monoid

4

名稱num在LOOP評估期間表示相同的結合。 也許你想寫:

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

得到你的意思。

+0

這將適用於這裏的簡化示例,但不適用於實際使用情況,需要在其中收集詞法關閉循環構造。 –

+0

@ClaytonStanley嗯,實際上你可以用'(lambda(idx)...)'替換''不斷'',並用'(alexandria:iota ...)'替換'(list ...)'。如果有更多的迭代變量,你可以添加更多的列表。實際上這是FP方式,所以如果環路中沒有狀態變化,你總是可以使用它。 –