Capture value of variable on lambda creation
Asked Answered
F

3

6

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.

Firework answered 3/11, 2014 at 0:21 Comment(1)
I propose to read a bit about LET.Offshore
P
8

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)))
Preston answered 3/11, 2014 at 4:23 Comment(2)
Solved! I was doing the iteration construct like in your second example, and getting all closures with the value of the last index. Thanks Joshua!Firework
@DiogoFranco Yeah, there's a some deliberate ambiguity (implemention-dependent behavior) concerning how many binding forms there are in some iterations constructs. I've added a bit more to my answer to explain why.Preston
M
4

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> 
Melessa answered 3/11, 2014 at 1:16 Comment(2)
Oh, but i want the exact opposite. In my example above, i would like the (funcall f) to return 10 forever, regardless of what happens to "i". I'm trying to bind the value of "i" when the function was created to it's body.Firework
@DiogoFranco The first one of these will work for you. Just don't share the scope of the local copy of i with a second function. (If you intialize the let from the outer i, you'll end up with two i variables: the original outside, and a private copy created by let.)Tubular
S
4

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
Sheets answered 3/11, 2014 at 6:39 Comment(1)
This one actually adds another possible point of confusion, since *i* is declared with defparameter, and thus globally declared special. That means that (let ((*i* *i*)) (lambda () *i)) wouldn't work. (That's not the same as what you did, but it might be good to mention that if the let bound a special variable, there'd be issues.Preston

© 2022 - 2024 — McMap. All rights reserved.