Common Lisp 作用域的微妙之处(动态 vs 词法)

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

ace*_*ent 6

让我们一步一步来。

表格 1

(defun fn ()
  (print x))
Run Code Online (Sandbox Code Playgroud)

这定义了fn.

由于引用的x变量没有在词法上声明,我们会收到警告。

自由引用变量通常在本地special使用自由声明1假设。这在标准中没有定义,但大多数实现都会这样做并发出警告。

相同的原则适用于评估(setq x ...)文件或 REPL 中的顶级表单。

这些情况都不应该全局声明xspecial.

表格 2

(describe 'x)
Run Code Online (Sandbox Code Playgroud)

x只是一个符号。全球没有发生任何事情。

表格 3

(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不特殊时的词法。

表格 4

(defvar x 'dinamic_global_binding)
Run Code Online (Sandbox Code Playgroud)

全局声明xspecial并将其设置symbol-valuedinamic_global_binding

现在每次将符号x用作变量都会受到这个全局 special声明的影响。从现在开始,代码没有标准的方法来绑定或引用命名x为词法变量的变量。

表格 5

(describe 'x)
Run Code Online (Sandbox Code Playgroud)

我们现在观察前一种形式的副作用。x全局特殊的,它的当前动态值为dinamic_global_binding

表格 6

(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都是特殊的。?




  1. 如果声明以执行绑定的形式赋予建立绑定的意义,则声明是绑定的,否则是自由的,即,如果它仅赋予绑定的引用/使用意义。

所以,下面的例子:

(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错误信号。