4

In the following loop:

(let ((funs (loop for i upto 3 do (print i) collect #'(lambda () i))))
  (loop for fun in funs collect (funcall fun)))

i would intuitively think i would get a list of four closures which return the numbers 0 1 2 and 3 upon being called, but this is what i get:

>> 0 
>> 1 
>> 2 
>> 3
=> (4 4 4 4)

But rebinding the i locally to something else:

(let ((funs (loop for i upto 3 do (print i) collect (let ((x i))
                          #'(lambda () x)))))
  (loop for fun in funs collect (funcall fun)))

works as expected:

>> 0 
>> 1 
>> 2 
>> 3
=> (0 1 2 3)

So each of the functions return 4, why are all return values the same, and why 4?

update

This seems to be a question about lambda actually. See below:

(setq dynamic-var 8
  funs ())
(push (lambda () dynamic-var) funs)
(incf dynamic-var)
(push (lambda () dynamic-var) funs)
(mapcar #'funcall funs)         ;(9 9)
3
  • 4
    in fact, that is not really common-lisp specific question. For example, here is one of the most popular questions for javascript job interviews: what would be the output of the following code var fns = []; for (var i = 0; i < 5; i++) { fns.push(() => i) }; fns.map(it => it()) Commented Dec 20, 2019 at 9:15
  • 1
    Great example! It helped me have a better understanding of the problem. My misunderstanding was that i was thinking by pushing a new function to some lists (fns) i am taking a snapshot from the state of the iteration variable (i). This holds also in Python: [fn() for fn in [lambda: i for i in range(5)]] gives: [4, 4, 4, 4, 4]. Commented Dec 20, 2019 at 12:49
  • 2
    @student: that's right. if we want to make a snapshot, we have to do it explicitly (by another LET binding for example). Commented Dec 21, 2019 at 10:31

1 Answer 1

8

What does

(let (i      ; a counter variable
      f)     ; a list of functions
  (setf i 1)
  (push (lambda () i) f)
  (setf i 2)
  (push (lambda () i) f)

  (mapcar #'funcall f))

return?

How about:

(let (i
      f)
  (setf i 1)
  (push (lambda () i) f)
  (let (i)
    (setf i 2)
    (push (lambda () i) f))

  (mapcar #'funcall f))

Which is the LOOP model?

See also:

CL-USER 42 > (let (f)
               (dotimes (i 10 (mapcar #'funcall (reverse f)))
                 (push (lambda () i) f)))
(10 10 10 10 10 10 10 10 10 10)

CL-USER 43 > (let (f)
               (dotimes (i 10 (mapcar #'funcall (reverse f)))
                 (push (let ((i i))
                         (lambda () i))
                       f)))
(0 1 2 3 4 5 6 7 8 9)

The Common Lisp standard says for DOTIMES:

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.

you write:

i would intuitively think i would get a list of four closures which return the numbers 0 1 2 and 3 upon being called

This intuition is only partly correct. You get four closures, but in this case they all share one variable binding. Thus they can only see the current binding of this one variable. In Common Lisp this binding is mutable and closures see the current binding, not the one of the initial binding of closure creation time.

Your intuition would be right, when each closure had its own variable binding.

Additional answer: why is this Lisp returning 10 ?

(PROGN
  (SETQ I (THE INTEGER (1+ (THE INTEGER I))))
  (WHEN (>= (THE INTEGER I)
            (THE INTEGER #:|dotimes-count-1075|))
    (GO #:|dotimes-end-tag1080|)))

Above is a part of the macro expansion of the dotimes construct. As you can see it first increments the variable and then tests for >=. Thus it exits when I is >= 10. Thus the last value of I is 10. Later after exiting the dotimes you are retrieving the value of I and then it's 10.

Sign up to request clarification or add additional context in comments.

3 Comments

It seems to me this behavior has actually nothing to do with iteration tools, but with the lambdas. Please see my update
But why i get 5 (or in your dotimes case 10) although the last value has been 1- that?
@Student: maybe because the particular implementation counts up and terminates on = 10? That's a detail. Don't depend on it. If you want to see what an implementation does, you can always check the source code or see the macro expansion of a form.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.