I think that Lars Brinkhoff's answer answers this most directly by appealing to the HyperSpec. You also might have a look at Chapter 6. Variables in Peter Seibel's Practical Common Lisp.
However, let's also consider what could we do to test this? One of the advantages of languages with lexical scoping and lexical closures is that the same binding can be shared between closures.
One binding referenced by multiple closures
For instance, we can bind a single variable x
(there's no question that there's only one x
here) and return two closures that access it:
(defun incrementer-and-getter (value)
(let ((x value))
(values (lambda ()
(setq x (+ 1 x)))
(lambda ()
x))))
Then, we can see that they refer to the same binding when we use the closures:
(multiple-value-bind (inc get)
(incrementer-and-getter 23)
(list (funcall get)
(funcall inc)
(funcall get)))
; => (23 24 24)
Multiple bindings with nested let
s
Now we can do something similar to test how many bindings there are in the cases that you gave:
(defun test2 ()
(let (get-outer
set-outer
get-inner
set-inner)
(let ((hi 'outer))
(setq get-outer (lambda () hi)
set-outer (lambda (new) (setq hi new)))
(let ((hi 'inner))
(setq get-inner (lambda () hi)
set-inner (lambda (new) (setq hi new)))))
(values get-outer
set-outer
get-inner
set-inner)))
(multiple-value-bind (get-outer set-outer get-inner set-inner)
(test2)
(list (funcall get-outer) ; retrieve outer
(funcall get-inner) ; retrieve inner
(funcall set-outer 'new-outer) ; update outer
(funcall set-inner 'new-inner) ; update inner
(funcall get-outer) ; retrieve outer
(funcall get-inner))) ; retrieve inner
; => (OUTER INNER NEW-OUTER NEW-INNER NEW-OUTER NEW-INNER)
The inner and outer bindings are different.
A single binding updated with setq
Now for the multiple setq
case:
(defun test3 ()
(let (get-first
set-first
get-second
set-second)
(let ((hi 'first))
(setq get-first (lambda () hi)
set-first (lambda (new) (setq hi new)))
(setq hi 'second)
(setq get-second (lambda () hi)
set-second (lambda (new) (setq hi new))))
(values get-first
set-first
get-second
set-second)))
(multiple-value-bind (get-first set-first get-second set-second)
(test3)
(list (funcall get-first)
(funcall get-second)
(funcall set-first 'new-first)
(funcall get-first)
(funcall get-second)
(funcall set-second 'new-second)
(funcall get-first)
(funcall get-second)))
(multiple-value-bind (get-first set-first get-second set-second)
(test3)
(list (funcall get-first)
(funcall get-second)
(funcall set-first 'new-first)
(funcall set-second 'new-second)
(funcall get-first)
(funcall get-second)))
; => (SECOND SECOND NEW-FIRST NEW-FIRST NEW-FIRST NEW-SECOND NEW-SECOND NEW-SECOND)
Here, both get-first
and get-second
return the same value, and both set-first
and set-second
update that value. The closures close over the same binding.
Each call to a function establishes new bindings
For the recursive case, we have to be a bit sneakier, but we can still check this:
(defparameter *closures* '())
(defun recurse (n)
(push (lambda () n) *closures*)
(push (lambda (new) (setq n new)) *closures*)
(unless (zerop n)
(recurse (1- n))))
(recurse 1) ; put four closures into *closures*
Now we can take those back out and see what happens:
;; remember we pushed these in, so they're in backwards
;; order, compared to everything else we've done.
(destructuring-bind (set-y get-y set-x get-x)
*closures*
(list (funcall get-x)
(funcall get-y)
(funcall set-x 'new-x)
(funcall set-y 'new-y)
(funcall get-x)
(funcall get-y)))
; => (1 0 NEW-X NEW-Y NEW-X NEW-Y)
There is a new binding for each invocation of the function, so the closures reference different bindings.
Common confusions in iteration constructs
For what it's worth, it's not too hard to get used to this behavior (if it's at all surprising in the first place). However, even seasoned Lisp veterans can get tripped up on the behavior in iteration constructs. These kind of cases suddenly make it very important to know whether, e.g., do
establishes a new binding for each iteration, or whether it updates the same binding. What should the following code print, 10987654321
or 0000000000
?
(let ((closures '()))
(do ((x 10 (1- x)))
((zerop x))
(push (lambda () x) closures))
(map nil (lambda (closure)
(print (funcall closure)))
closures))
In the case of do
, it's 0000000000
, because (emphasis added):
All the step-forms, if supplied, are evaluated, from left to right, and the resulting values are assigned to the respective vars.
This comes up a lot in loop
where it's implementation dependent, but people expect different behavior from other iteration macros, too. See, for instance, this StackOverflow question:
or these threads on comp.lang.lisp
: