我正在阅读 Peter Seibel 的《Practical Common Lisp》一书。
第 6 章“变量”部分 “词法变量和闭包”和“动态变量,又名特殊变量”。 http://www.gigamonkeys.com/book/variables.html
我的问题是,这两部分中的示例都显示了 (let ...) 如何隐藏全局变量,并没有真正说明动态变量和词法变量之间的区别。
我理解闭包是如何工作的,但我真的不明白 let 在这个例子中有什么特别之处:
(defvar *x* 10)
(defun foo ()
(format t "Before assignment~18tX: ~d~%" *x*)
(setf *x* (+ 1 *x*))
(format t "After assignment~18tX: ~d~%" *x*))
(defun bar ()
(foo)
(let ((*x* 20)) (foo))
(foo))
CL-USER> (foo)
Before assignment X: 10
After assignment X: 11
NIL
CL-USER> (bar)
Before assignment X: 11
After assignment X: 12
Before assignment X: 20
After assignment X: 21
Before assignment X: 12
After assignment X: 13
NIL
我觉得这里没什么特别的。 bar 中的外部 foo 递增全局 x,foo 被 let 包围在 bar 中增加阴影 x。有什么大不了的?我不明白这应该如何解释词法变量和动态变量之间的区别。然而这本书继续是这样的:
So how does this work? How does LET know that when it binds x it's supposed to create a dynamic binding rather than a normal lexical binding? It knows because the name has been declared special.12 The name of every variable defined with DEFVAR and DEFPARAMETER is automatically declared globally special.
如果 let 使用“正常词法绑定(bind)” 绑定(bind) x 会发生什么?总而言之,动态绑定(bind)和词法绑定(bind)之间有什么区别?这个示例在动态绑定(bind)方面有何特殊之处?
最佳答案
这是怎么回事?
你说:感觉这里没什么特别的。 bar
中的外部 foo
递增全局 x
,foo
被 let
包围在 bar
中增加阴影 x
。有什么大不了的?
这里发生的特殊是LET
可以隐藏*x*
的值。使用词法变量是不可能的。
代码通过 DEFVAR
将 *x*
声明为特殊。
在 FOO
中,现在 *x*
的值是动态查找的。 FOO
将采用 *x*
的当前动态绑定(bind),或者,如果没有,则采用符号 *x 的符号值*
。例如,可以使用 LET
引入新的动态绑定(bind)。
另一方面,词法变量必须存在于词法环境中的某处。 LET
、LAMBDA
、DEFUN
等可以引入这样的词法变量。请参阅此处以三种不同方式引入的词法变量 x
:
(let ((x 3))
(* (sin x) (cos x)))
(lambda (x)
(* (sin x) (cos x)))
(defun baz (x)
(* (sin x) (cos x)))
如果我们的代码是:
(defvar x 0)
(let ((x 3))
(* (sin x) (cos x)))
(lambda (x)
(* (sin x) (cos x)))
(defun baz (x)
(* (sin x) (cos x)))
然后 X
在上述所有三种情况下都是特殊,因为 DEFVAR
声明声明了 X
成为特殊 - 全局所有级别。因此,存在将特殊 变量声明为*X*
的约定。因此,根据惯例,只有周围有星号的变量才是特殊。这是一个有用的约定。
然后在您的代码中:
(defun bar ()
(foo)
(let ((*x* 20))
(foo))
(foo))
由于 *x*
已通过上面的 DEFVAR
在您的代码中声明为 special,因此 LET
结构为 *x*
引入了一个新的动态绑定(bind)。 FOO
然后被调用。由于在 FOO
中,*x*
使用了动态绑定(bind),它会查找当前绑定(bind)并发现 *x*
动态绑定(bind)到 20
。
特殊变量的值在当前动态绑定(bind)中找到。
本地特殊声明
还有局部特殊
声明:
(defun foo-s ()
(declare (special *x*))
(+ *x* 1))
如果变量已被DEFVAR
或DEFPARAMETER
声明为special,则局部special
声明可以被省略。
词法变量直接引用变量绑定(bind):
(defun foo-l (x)
(+ x 1))
让我们在实践中看看:
(let ((f (let ((x 10))
(lambda ()
(setq x (+ x 1))))))
(print (funcall f)) ; form 1
(let ((x 20)) ; form 2
(print (funcall f))))
这里所有的变量都是词法的。在 form 2 中,LET
不会隐藏我们函数 f
中的 X
。它不能。该函数使用词法绑定(bind)变量,由 LET ((X 10)
引入。用 form 2 中的另一个词法绑定(bind) X
包围调用对我们的功能没有影响。
让我们试试特殊变量:
(let ((f (let ((x 10))
(declare (special x))
(lambda ()
(setq x (+ x 1))))))
(print (funcall f)) ; form 1
(let ((x 20)) ; form 2
(declare (special x))
(print (funcall f))))
现在怎么办?这行得通吗?
没有!
第一种形式 调用该函数并尝试查找X
的动态值,但没有找到。我们在 form 1 中得到一个错误:X
未绑定(bind),因为没有生效的动态绑定(bind)。
Form 2 可以工作,因为带有 special
声明的 LET
为 X
引入了动态绑定(bind)。
关于lisp - Common Lisp 中的动态变量和词法变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/463463/