我认为Lars Brinkhoff 的回答通过诉诸 HyperSpec 最直接地回答了这个问题。您还可以查看第 6 章。Peter Seibel 的Practical Common Lisp中的变量。
但是,让我们也考虑一下我们可以做些什么来测试这个?具有词法作用域和词法闭包的语言的优点之一是可以在闭包之间共享相同的绑定。
多个闭包引用的一个绑定
例如,我们可以绑定一个变量x
(毫无疑问,这里只有一个x
)并返回两个访问它的闭包:
(defun incrementer-and-getter (value)
(let ((x value))
(values (lambda ()
(setq x (+ 1 x)))
(lambda ()
x))))
然后,我们可以看到当我们使用闭包时它们引用了相同的绑定:
(multiple-value-bind (inc get)
(incrementer-and-getter 23)
(list (funcall get)
(funcall inc)
(funcall get)))
; => (23 24 24)
嵌套let
s 的多个绑定
现在我们可以做一些类似的事情来测试在你给出的情况下有多少绑定:
(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)
内部和外部绑定是不同的。
单个绑定更新为setq
现在对于多重setq
情况:
(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)
在这里,两者都get-first
返回get-second
相同的值,并且两者都set-first
更新set-second
该值。闭包在同一个绑定上关闭。
对函数的每次调用都会建立新的绑定
对于递归的情况,我们必须稍微偷偷摸摸,但我们仍然可以检查一下:
(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*
现在我们可以把它们取出来看看会发生什么:
;; 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)
每次调用函数都有一个新的绑定,因此闭包引用不同的绑定。
迭代构造中的常见混淆
对于它的价值,习惯这种行为并不难(如果它一开始就令人惊讶的话)。然而,即使是经验丰富的 Lisp 老手也可能会被迭代构造中的行为绊倒。这类情况突然变得非常重要,例如了解是否do
为每次迭代建立一个新的绑定,或者是否更新相同的绑定。下面的代码应该打印什么, 10987654321
或者0000000000
?
(let ((closures '()))
(do ((x 10 (1- x)))
((zerop x))
(push (lambda () x) closures))
(map nil (lambda (closure)
(print (funcall closure)))
closures))
在 的情况下do
,它是0000000000
,因为(强调):
如果提供了所有步骤形式,则从左到右进行评估,并将结果值分配给相应的变量。
这在依赖于实现的地方出现了很多loop
,但人们也期望与其他迭代宏有不同的行为。例如,请参阅这个 StackOverflow 问题:
或这些线程comp.lang.lisp
: