2014-11-03 91 views
5

如果我們賦值給一個變量:捕捉值

(setf i 10) 

,然後創建一個lambda函數關閉了它:

(setf f #'(lambda() i)) 

我們的行爲

(incf i) ;=> 11 
(funcall f) ;=> 11 

相反,我想函數總是返回值i在函數wa創建。例如:

(incf i) ;=> 11 
(funcall f) ;=> 10 

本質上,我想將i變成lambda體內的字面值。這在Common Lisp中可以做到嗎?原因是我在一個循環內部創建了多個lambda表達式,並且需要在它們的body中使用索引,而不需要在創建之後改變它們。

+3

Dolist,例如可以用下面的一種來定義建議閱讀一下關於「LET」。 – 2014-11-03 01:04:51

回答

8

只需綁定一個變量與值的副本。例如:

(let ((i i)) 
    (lambda() i)) 

這實際上是用的迭代結構的一項重要技術,因爲像

(loop for i from 1 to 10 
    collecting (lambda() i)) 

可以在相同的變量返回10封,所以寫有必要:

(loop for i from 1 to 10 
    collecting (let ((i i)) (lambda() i))) 

如果你真的只需要一個返回值的函數,你也可以使用constantly(但我期望真實的用例更復雜):

(loop for i from 1 to 10 
    collecting (constantly i)) 

迭代形式的歧義實際上是由標準指定的,在某些情況下。例如,對於dotimesdolist

這是實現相關dotimes是否建立VAR的一個新的綁定在每次迭代或者是否建立之初的VAR綁定一次,然後分配給它的任何後續的迭代。

更原始do,然而,實際上規定了有一組綁定的形式,和(強調),它們在每次迭代更新:

在每次的開始除第一次迭代外,變量更新如下。…

這種不明確性給實現帶來了更多的靈活性。

(defmacro dolist ((var list &optional result) &body body) 
    `(progn (mapcar #'(lambda (,var) 
         ,@(ex:body-declarations body) 
         (tagbody 
         ,@(ex:body-tags-and-statements body))) 
        ,list) 
      (let ((,var nil)) 
      ,result))) 

(defmacro dolist ((var list &optional result) &body body) 
    (let ((l (gensym (string '#:list-)))) 
    `(do* ((,l ,list (rest ,l)) 
      (,var (first ,l) (first ,l))) 
      ((endp ,l) ,result) 
     ,@body))) 
+0

解決!我像第二個例子那樣執行迭代構造,並使用最後一個索引的值獲取所有閉包。感謝Joshua! – 2014-11-03 12:07:38

+0

@DiogoFranco是的,有一些故意模糊(實現依賴行爲)關於在一些迭代構造中有多少綁定形式。爲了解釋原因,我在答案中增加了一些內容。 – 2014-11-03 13:35:17

4

這並不完全清楚你想要什麼。如果你想創建一個範圍內存在共享變量i,你可以用let做到這一點。

CL-USER> (let ((i 10)) 
     (defun show-i() i) 
     (defun inc-i() (incf i)) 
     (defun dec-i() (decf i))) 
DEC-I 
CL-USER> (show-i) 
10 
CL-USER> (inc-i) 
11 
CL-USER> (show-i) 
11 
CL-USER> (dec-i) 
10 
CL-USER> (dec-i) 
9 
CL-USER> (show-i) 
9 
CL-USER> 

如果您正在尋找使用動態範圍變量,則可以使用直線型defvar

CL-USER> (defvar *a* 10) 
*A* 
CL-USER> (defun show-a() *a*) 
SHOW-A 
CL-USER> (show-a) 
10 
CL-USER> *a* 
10 
CL-USER> (incf *a*) 
11 
CL-USER> (incf *a*) 
12 
CL-USER> (show-a) 
12 
CL-USER> 
+0

哦,但我想要完全相反。在我上面的例子中,我希望(funcall f)永遠返回10,無論發生什麼「我」。我試圖將創建函數時的「我」的值綁定到它的正文。 – 2014-11-03 01:24:08

+1

@DiogoFranco其中第一個將爲你工作。只是不要與第二個函數共享'i'本地副本的範圍。 (如果你初始化''let'從外部'i',你將最終得到兩個'i'變量:原始的外部和'let'創建的私人副本。) – Leushenko 2014-11-03 01:30:46

4

萬一上述兩個答案不透明度不夠的:

(defparameter *i* 10) 
;;Before you modify *i* 
(defvar f (let ((i *i*)) 
       #'(lambda() i))) 
;;Now f will always return 10 
(funcall f) => 10 
(incf *i*) => 11 
(funcall f) => 10 
+0

這個實際上增加了另一個可能的混淆點,因爲'* i *'是用'defparameter'聲明的,因此*全局*聲明爲特殊的。這意味着'(let((* i * * i *))(lambda()* i))'*不會工作。 (這和你所做的不一樣,但是如果讓一個特殊的變量綁定,可能會有問題。 – 2014-11-03 10:57:43