在lambda创建时捕获变量的值

时间:2014-11-03 00:21:29

标签: variables lambda scope lisp common-lisp

如果我们为变量赋值:

(setf i 10)

然后创建一个关闭它的lambda函数:

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

我们有行为

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

相反,我希望函数在创建函数时始终返回i的值。 E.g:

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

基本上我想将i转换为lambda体内的文字。这可以在Common Lisp中完成吗?原因是我在一个循环中创建了多个lambda,并且需要在它们的主体中使用索引,而不会在创建后改变它们。

3 个答案:

答案 0 :(得分:8)

只需将变量与值的副本绑定即可。 E.g:

(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实际上指定了表单的一组绑定,并且在每次迭代时都会更新它们(强调添加):

  

在第一次以外的每次迭代开始时,vars按更新,如下所示。 ...

这种模糊性使实现更具灵活性。例如, Dolist 可以使用以下任一方式定义:

(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)))

答案 1 :(得分: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> 

答案 2 :(得分: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