Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capture value of variable on lambda creation

If we assign a value to a variable:

(setf i 10)

and then a create a lambda function closing over it:

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

We have the behavior

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

Instead, I would like the function to always return the value of i at the time that the function was created. E.g.:

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

Essentially i would like to turn i into a literal inside the lambda body. Is this possible to do in Common Lisp? The reason is that I'm creating more than one lambda inside a loop, and need to use the index in their bodies, without them varying after creation.

like image 583
Diogo Franco Avatar asked Nov 03 '14 00:11

Diogo Franco


Video Answer


3 Answers

Just bind a variable with a copy of the value. E.g.:

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

This is actually an important technique with iteration constructs, because something like

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

may return ten closures over the same variables, so it becomes necessary to write:

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

If you really only need a function that returns the value, you could also use constantly (but I expect the real use case is more complicated):

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

The ambiguity in iteration forms is actually specified by the standard, in some cases. E.g., for dotimes, dolist

It is implementation-dependent whether dotimes establishes a new binding of var on each iteration or whether it establishes a binding for var once at the beginning and then assigns it on any subsequent iterations.

The more primitive do, however, actually specifies that there is one set of bindings for the form, and that they are updated at each iteration (emphasis added):

At the beginning of each iteration other than the first, vars are updated as follows. …

This ambiguity gives implementations a bit more flexibility. Dolist, for instance could be defined with either of the following:

(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)))
like image 124
Joshua Taylor Avatar answered Sep 27 '22 19:09

Joshua Taylor


It's not entirely clear what you want here. If you want to create a scope within which there exists a shared variable i, you can do that with a 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> 

If you're looking to use dynamically scoped variables, you can use straight-up 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> 
like image 34
Inaimathi Avatar answered Sep 27 '22 20:09

Inaimathi


Just in case the above two answers don't suffice in clarity:

(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
like image 25
automaton Avatar answered Sep 27 '22 20:09

automaton