4

Why do these forms behave this way?

CL-USER>
(setf *closures*
      (loop for num in (list 1 2 3 4)
            collect (lambda ()
                      num)))
(     
#<COMPILED-LEXICAL-CLOSURE #x302004932E1F>
#<COMPILED-LEXICAL-CLOSURE #x302004932DCF>
#<COMPILED-LEXICAL-CLOSURE #x302004932D7F>
#<COMPILED-LEXICAL-CLOSURE #x302004932D2F>)
CL-USER> 
(funcall (first *closures*))
4
CL-USER> 
(funcall (second *closures*))
4

I would have expected the first funcall to return 1, and the second to return 2, etc. This behavior is consistent with both Clozure Common Lisp and Steel-Bank Common Lisp implementations.

If I rework the loop macro to a version using dolist, what I'd expect is what's returned:

(setf *closures*
      (let ((out))
        (dolist (item (list 1 2 3 4) (reverse out))
          (push (lambda () item) out))))
(
#<COMPILED-LEXICAL-CLOSURE #x302004A12C4F>
#<COMPILED-LEXICAL-CLOSURE #x302004A12BFF>  
#<COMPILED-LEXICAL-CLOSURE #x302004A12BAF>
#<COMPILED-LEXICAL-CLOSURE #x302004A12B5F>)
CL-USER> 
(funcall (first *closures*))
1
CL-USER> 
(funcall (second *closures*))
2

CL-USER>

What's going on with the loop macro version?

Clayton Stanley
  • 7,513
  • 9
  • 32
  • 46

2 Answers2

8

num is same variable shared by all lambdas.

Use

(setf *closures*
  (loop for num in (list 1 2 3 4)
        collect (let ((num1 num))
                  (lambda ()
                    num1))))

num1 is fresh variable for each iteration.

As of dolist, "It is implementation-dependent whether dolist 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." (CLHS, Macro DOLIST). So it may work on one implementation and fail on other.

monoid
  • 1,661
  • 11
  • 16
4

The name num represents the same binding during the evaluation of LOOP. Maybe you want to write:

(mapcar 'constantly (list 1 2 3 4))

to get what you meant.

Charles Lew
  • 118
  • 5
  • This would work for the simplified example here, but not for the actual use case, where I need to collect lexical closures in the looping construct. – Clayton Stanley Mar 30 '13 at 18:12
  • @ClaytonStanley Hmm, actually you can replace `constantly` with a `(lambda (idx) ...)`, and replace the `(list ...)` with `(alexandria:iota ...)`. If there's more iteration variables you may add more lists. Actually this is the FP way, so you always can use this if there's no state changing in the loop... – Charles Lew Apr 01 '13 at 06:31