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

3

阅读有关声明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

为什么在变量 x 被 defvar 前,第三次调用 fn 会输出 DINAMIC_1ST_BINDING,而在变量 x 被 defvar 后,它会输出 LEXICAL_1ST_BINDING

1
你已经多次创建了抄袭其他来源文本的标签维基。如果你要创建标签维基,比如这个,它直接从这里复制而来,请在你的条目中包含一个指向源的链接。 - LittleBobbyTables - Au Revoir
3个回答

6

让我们一步一步来。

表单 1

(defun fn ()
  (print x))

这里定义了fn

由于引用的变量x没有词法声明,因此会收到警告。

通常情况下,自由引用的变量在本地被假定为具有自由声明1特别。这在标准中没有定义,但大多数实现都会这样做并发出警告。

同样的原则适用于在文件或REPL中计算顶层表达式(setq x ...)

这些情况都不应该将x作为special进行全局声明。

表格 2

(describe 'x)

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))

第一个绑定到x的是局部声明的special。因此,fn将选择它。

第二个绑定到x的是同样的内容,只是为x创建了一个新的动态绑定,在调用fn后解开。

第三个绑定到x的是词法绑定,因为每个绑定都是词法的,除非有全局的special声明或者有一个局部special绑定声明1

因此,fn会选择最近的x动态绑定,即选择dinamic_1st_binding

print表单中的x使用包含的x的意义,因此在声明为special时选择special x,否则选择词法x

表单4

(defvar x 'dinamic_global_binding)

这个全局声明将x声明为special,并将其symbol-value设置为dinamic_global_binding

现在每次使用符号x作为变量时,都会带有这个全局special声明。从此刻开始,代码无法以标准方式绑定或引用名为x的词法变量。

形式5

(describe 'x)

我们现在观察一下上一个形式的副作用。变量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))

x 的所有绑定都是特殊的。∎




  1. 如果声明赋予了在执行绑定的形式中建立绑定的含义,则该声明被视为 绑定 ,否则被视为 自由 ,即仅对绑定的引用/使用赋予含义。

因此,以下示例:

(defun fn2 ()
  (print y))

(let ((y 1))
  (fn2)
  (locally (declare (special y))
    (fn2)))

不会使let动态绑定y,它只会通过free声明使代码词法化,在局部作用域下引用y时将其视为一个动态变量。在这种情况下,对fn2的两次调用都将发出错误信号。

以下内容:

(let ((y 1))
  (declare (special y))
  (print y)
  (let ((y 2))
    (print y)
    (locally (declare (special y))
      (print y))
    (print y)))

将打印:

1
2
1
2

第一个对 y 的绑定有一个 bound special 声明,因此它是动态绑定。

第二个对 y 的绑定没有 bound special 声明,且 y 不是全局的 special,因此它是词法绑定。

因此,对 y 的访问也受到(缺失的)有关 y 的声明的限制。

第二个声明是一个 free 声明,使得对变量 y 的引用被视为动态的。因此,一个名为 y 的变量的上一个绑定不会被这个 free 声明改变。

以下是一个例子:

(let ((y 1))
  (print y)
  (locally (declare (special y))
    (print y)))

第二个print表达式会失败,因为它对y的引用是动态的,并且没有为y建立动态绑定,即y是未绑定的,会触发一个unbound-variable错误。


2

情况一:x 有一个词法绑定。

情况二:x 没有词法绑定。只是你有这个符号,但这是误导性的,因为它明显显示最后一个 x 也是动态绑定的。


2

Defvar 等人 宣称这个变量是全局特殊的。 这意味着即使你省略special声明,该变量的每个绑定都是动态的。

在第二次运行中,将x绑定到'lexical-1st-binding会导致误导,因为它仍然是一个动态绑定。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接