Pau*_*omé 3 lisp common-lisp scoping dynamic-scope lexical-scope
在阅读了有关声明SPECIAL 的文档、特殊运算符LET、宏DEFVAR以及 StackOverflow 上关于 Common Lisp 中动态与词法范围的几个问题之后,例如,this 之后,我仍然无法理解以下行为在 SBCL 中评估这些形式。
;; x is a free variable
CL-USER> (defun fn ()
(print x))
; in: DEFUN FN
; (PRINT X)
;
; caught WARNING:
; undefined variable: X
;
; compilation unit finished
; Undefined variable:
; X
; caught 1 WARNING condition
FN
CL-USER> (describe 'x)
COMMON-LISP-USER::X
[symbol]
; No value
CL-USER> (let ((x 'dinamic_1st_binding))
(declare (special x))
(print x)
(fn)
(let ((x 'dinamic_2nd_binding))
(declare (special x))
(print x)
(fn))
(let ((x 'lexical_1st_binding))
(print x)
(fn))
(values))
DINAMIC_1ST_BINDING
DINAMIC_1ST_BINDING
DINAMIC_2ND_BINDING
DINAMIC_2ND_BINDING
LEXICAL_1ST_BINDING
DINAMIC_1ST_BINDING
; No value
;; x is defvar'ed as a top level form
CL-USER> (defvar x 'dinamic_global_binding)
X
CL-USER> (describe 'x)
COMMON-LISP-USER::X
[symbol]
X names a special variable:
Value: DINAMIC_GLOBAL_BINDING
; No value
CL-USER> (let ((x 'dinamic_1st_binding))
(declare (special x))
(print x)
(fn)
(let ((x 'dinamic_2nd_binding))
(declare (special x))
(print x)
(fn))
(let ((x 'lexical_1st_binding))
(print x)
(fn))
(values))
DINAMIC_1ST_BINDING
DINAMIC_1ST_BINDING
DINAMIC_2ND_BINDING
DINAMIC_2ND_BINDING
LEXICAL_1ST_BINDING
LEXICAL_1ST_BINDING
; No value
Run Code Online (Sandbox Code Playgroud)
为什么第三次调用fn,在变量x被 defvar'ed之前,打印,DINAMIC_1ST_BINDING而在变量x被 defvar'ed 之后它打印LEXICAL_1ST_BINDING?
让我们一步一步来。
(defun fn ()
(print x))
Run Code Online (Sandbox Code Playgroud)
这定义了fn.
由于引用的x变量没有在词法上声明,我们会收到警告。
自由引用变量通常在本地special使用自由声明1假设。这在标准中没有定义,但大多数实现都会这样做并发出警告。
相同的原则适用于评估(setq x ...)文件或 REPL 中的顶级表单。
这些情况都不应该全局声明x为special.
(describe 'x)
Run Code Online (Sandbox Code Playgroud)
x只是一个符号。全球没有发生任何事情。
(let ((x 'dinamic_1st_binding))
(declare (special x))
(print x)
(fn)
(let ((x 'dinamic_2nd_binding))
(declare (special x))
(print x)
(fn))
(let ((x 'lexical_1st_binding))
(print x)
(fn))
(values))
Run Code Online (Sandbox Code Playgroud)
第一个结合x是本地声明special。所以,fn会捡起来。
第二个x绑定to是相同的,只是创建一个新的动态绑定 for x,在调用 之后展开fn。
第三个绑定x是词法绑定,因为每个绑定都是词法绑定,除非绑定变量有全局 special声明或局部 special绑定声明1。
因此,fn选取 的最新动态绑定的x将选取dinamic_1st_binding。
形式中的xsprint使用 的任何封闭含义x,因此选择special x声明特殊时的含义和x不特殊时的词法。
(defvar x 'dinamic_global_binding)
Run Code Online (Sandbox Code Playgroud)
这全局声明x为special并将其设置symbol-value为dinamic_global_binding。
现在每次将符号x用作变量都会受到这个全局 special声明的影响。从现在开始,代码没有标准的方法来绑定或引用命名x为词法变量的变量。
(describe 'x)
Run Code Online (Sandbox Code Playgroud)
我们现在观察前一种形式的副作用。x是全局特殊的,它的当前动态值为dinamic_global_binding。
(let ((x 'dinamic_1st_binding))
(declare (special x))
(print x)
(fn)
(let ((x 'dinamic_2nd_binding))
(declare (special x))
(print x)
(fn))
(let ((x 'lexical_1st_binding))
(print x)
(fn))
(values))
Run Code Online (Sandbox Code Playgroud)
所有的绑定x都是特殊的。?
所以,下面的例子:
(defun fn2 ()
(print y))
(let ((y 1))
(fn2)
(locally (declare (special y))
(fn2)))
Run Code Online (Sandbox Code Playgroud)
不会动态let绑定y。它不仅使词法下的代码locally是指y通过把它当作一个动态变量自由宣言。在这种情况下,两个调用fn2都会发出错误信号。
下列:
(let ((y 1))
(declare (special y))
(print y)
(let ((y 2))
(print y)
(locally (declare (special y))
(print y))
(print y)))
Run Code Online (Sandbox Code Playgroud)
将打印:
1
2
1
2
Run Code Online (Sandbox Code Playgroud)
的第一个绑定y有一个绑定 special声明,所以它是一个动态绑定。
的第二个绑定y没有绑定 special声明,y也不是全局 绑定special,所以它是一个词法绑定。
因此,对 的访问y也受(缺少)关于 的声明的限制y。
第二个声明是一个自由声明,使得对变量的引用y被视为动态的。因此,y此自由声明不会更改与命名变量的直接先前绑定。
下面的例子:
(let ((y 1))
(print y)
(locally (declare (special y))
(print y)))
Run Code Online (Sandbox Code Playgroud)
将在第二种print形式中失败,因为它对 的引用y是动态的,并且没有为 建立动态绑定y,即y未绑定并发出unbound-variable错误信号。