在lambda创建时捕获变量的值

Dio*_*nco 5 lisp variables lambda scope common-lisp

如果我们为变量赋值:

(setf i 10)
Run Code Online (Sandbox Code Playgroud)

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

(setf f #'(lambda () i))
Run Code Online (Sandbox Code Playgroud)

我们有这种行为

(incf i)    ;=> 11
(funcall f) ;=> 11
Run Code Online (Sandbox Code Playgroud)

相反,我希望函数始终返回i创建函数时的值.例如:

(incf i)    ;=> 11
(funcall f) ;=> 10
Run Code Online (Sandbox Code Playgroud)

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

Jos*_*lor 8

只需将变量与值的副本绑定即可.例如:

(let ((i i))
  (lambda () i))
Run Code Online (Sandbox Code Playgroud)

这实际上是迭代结构的一个重要技术,因为类似于

(loop for i from 1 to 10
   collecting (lambda () i))
Run Code Online (Sandbox Code Playgroud)

可能会在相同的变量上返回十个闭包,因此有必要写:

(loop for i from 1 to 10
  collecting (let ((i i)) (lambda () i)))
Run Code Online (Sandbox Code Playgroud)

如果你真的只需要一个返回值的函数,你也可以不断使用(但我希望真正的用例更复杂):

(loop for i from 1 to 10
  collecting (constantly i))
Run Code Online (Sandbox Code Playgroud)

在某些情况下,迭代表单中的歧义实际上是由标准指定的.例如,对于dotimes,dolist

它是依赖于实现的,是否dotimes在每次迭代时建立var的新绑定,或者它是否在开始时为var建立一次绑定,然后在任何后续迭代上分配它.

更原始的,但是,实际上规定了有一组绑定的形式,并且使它们在每次迭代(强调)更新日期:

在除第一次之外的每次迭代开始时,变量如下更新....

这种模糊性使实现更具灵活性. 例如,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)))
Run Code Online (Sandbox Code Playgroud)

(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)))
Run Code Online (Sandbox Code Playgroud)