如何解决如何解释词法绑定与动态绑定?
我阅读了有关绑定的相关post,但仍有疑问。
这是我找到的以下示例。谁能告诉我结论是否正确?
(i) 中 x 的动态绑定:
(defun j ()
(let ((x 1))
(i)))
(defun i ()
(+ x x))
> (j)
2
i2 中 x 的词法绑定:
(defun i2 (x)
(+ x x))
(defun k ()
(let ((x 1))
(i2 2)))
> (k)
4
ANSI CL 中没有全局词法变量,因此执行动态绑定:
(setq x 3)
(defun z () x)
> (let ((x 4)) (z))
4
动态绑定,似乎绑定到词法范围的变量:
(defvar x 1)
(defun f (x) (g 2))
(defun g (y) (+ x y))
> (f 5)
7
基于以上测试,CL首先尝试词法绑定。如果环境中没有词法匹配,则 CL 尝试动态绑定。似乎任何以前的词法作用域变量都可用于动态绑定。这样对吗?如果不是,行为是什么?
解决方法
总而言之:不,CL 永远不会“先尝试一种绑定,然后再尝试另一种”:而是在编译时确定哪种绑定有效,并引用该绑定。此外,变量绑定和引用始终是词法的,除非有一个有效的特殊声明,在这种情况下它们是动态的。唯一不词法明显的是是否存在有效的特殊声明是全局特殊声明,通常通过defvar
/ defparameter
执行,这可能不可见(例如,它们可能在其他源文件中)。
它决定像这样绑定有两个很好的理由:
- 首先,这意味着阅读代码的人可以(除非可能在不可见的全局特殊声明的情况下)知道正在使用哪种绑定 - 对变量引用是否是动态的感到惊讶或该变量的词汇绑定很少是一种愉快的体验;
- 其次,这意味着良好的编译是可能的——除非编译器可以知道一个变量的绑定类型是什么,否则它永远无法编译好的代码来引用该变量,尤其是对于具有确定范围的词法绑定,其中变量引用通常可以完全编译。
一个重要的旁白:总是使用一种视觉上不同的方式来编写具有全局特殊声明的变量。所以永远不要说 (defvar x ...)
之类的东西:语言不禁止这样做,但在阅读代码时它只是灾难性的误导,因为在这种情况下,人们通常看不到特殊声明阅读代码。此外,将 *...*
用于全局特价,例如语言定义的全局特价以及其他人所做的。我经常将 %...%
用于 非全球 特价商品,但我所知道的对此没有最佳做法。为常量使用朴素的名字是没问题的(并且有很多由语言定义的例子),因为它们可能不受约束。
下面的详细示例和答案假设:
- Common Lisp(不是任何其他 Lisp);
- 引用的代码就是所有代码,所以没有额外的声明或类似的东西。
以下是一个全局特殊声明生效的示例:
;;; Declare *X* globally special,but establish no top-level binding
;;; for it
;;;
(defvar *x*)
(defun foo ()
;; FOO refers to the dynamic binding of *X*
*x*)
;;; Call FOO with no binding for *X*: this will signal an
;;; UNBOUND-VARIABLE error,which we catch and report
;;;
(handler-case
(foo)
(unbound-variable (u)
(format *error-output* "~&~S unbound in FOO~%"
(cell-error-name u))))
(defun bar (x)
;; X is lexical in BAR
(let ((*x* x))
;; *X* is special,so calling FOO will now be OK
(foo)))
;;; This call will therefore return 3
;;;
(bar 3)
这是一个存在非全局特殊声明的示例。
(defun foo (x)
;; X is lexical
(let ((%y% x))
(declare (special %y%))
;; The binding of %Y% here is special. This means that the
;; compiler can't know if it is referenced so there will be no
;; compiler message even though it is unreferenced in FOO.
(bar)))
(defun bar ()
(let ((%y% 1))
;; There is no special declaration in effect here for %Y%,so this
;; binding of %Y% is lexical. Therefore it is also unused,and
;; tere will likely be a compiler message about this.
(fog)))
(defun fog ()
;; FOG refers to the dynamic binding of %Y%. Therefore there is no
;; compiler message even though there is no apparent binding of it
;; at compile time nor gobal special declaration.
(declare (special %y%))
%y%)
;;; This returns 3
;;;
(foo 3)
请注意,在此示例中,词法上很明显应该对 %y%
有效的绑定是什么:仅查看函数本身就会告诉您需要了解的内容。
现在是对您的示例代码片段的评论。
(defun j ()
(let ((x 1))
(i)))
(defun i ()
(+ x x))
> (j)
<error>
这在 CL 中是非法的:x
未绑定在 i
中,因此调用 i
应发出错误信号(特别是 unbound-variable
错误)。>
(defun i2 (x)
(+ x x))
(defun k ()
(let ((x 1))
(i2 2)))
> (k)
4
这很好:i2
在词法上绑定 x
,因此从不使用由 k
建立的绑定。您可能会收到有关 k
中未使用变量的编译器警告,但这当然取决于实现。
(setq x 3)
(defun z () x)
> (let ((x 4)) (z))
<undefined>
这是 CL 中的未定义行为:setq
的可移植行为是改变 现有 绑定,而不是 创建 新绑定。您正试图用它来做后者,这是未定义的行为。许多实现允许 setq
在顶层像这样使用,它们可以创建本质上是全局词法、全局特殊的东西,或者做一些其他的事情。虽然在实践中与给定实现的顶级交互时经常这样做,但这并不能使其在语言中定义行为:程序永远不应该这样做。当你这样做时,我自己的实现会从隐藏的喷嘴中向程序员的大方向喷射白热的铅喷射。
(defvar x 1)
(defun f (x) (g 2))
(defun g (y) (+ x y))
> (f 5)
7
这是合法的。这里:
-
defvar
声明x
全局特殊,所以x
的所有绑定都是动态的,并建立了x
到1
的顶级绑定; - 在
f
其参数的绑定中,x
因此将是动态的,而不是词法的(没有前面的defvar
它将是词法的); - 在
g
中,对x
的自由引用将是对其动态绑定(如果没有前面的defvar
,它将是一个编译时警告(依赖于实现)和一个运行-时间错误(与实现无关)。
(defun j ()
(let ((x 1))
(i)))
(defun i ()
(+ x x))
> (j)
2
这实际上是 Common Lisp 中未定义的行为。标准中没有定义使用未定义变量(此处在函数 i
中)的确切后果。
CL-USER 75 > (defun j ()
(let ((x 1))
(i)))
J
CL-USER 76 > (defun i ()
(+ x x))
I
CL-USER 77 > (j)
Error: The variable X is unbound.
1 (continue) Try evaluating X again.
2 Return the value of :X instead.
3 Specify a value to use this time instead of evaluating X.
4 Specify a value to set X to.
5 (abort) Return to top loop level 0.
Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.
CL-USER 78 : 1 >
如您所见,Lisp 解释器 (!) 在运行时会报错。
现在:
(setq x 3)
SETQ
设置一个未定义的变量。这在标准中也没有完全定义。大多数编译器会抱怨:
LispWorks
;;;*** Warning in (TOP-LEVEL-FORM 1): X assumed special in SETQ
; (TOP-LEVEL-FORM 1)
;; Processing Cross Reference Information
;;; Compilation finished with 1 warning,0 errors,0 notes.
或SBCL
; in: SETQ X
; (SETQ X 1)
;
; caught WARNING:
; undefined variable: COMMON-LISP-USER::X
;
; compilation unit finished
; Undefined variable:
; X
; caught 1 WARNING condition
(defvar x 1)
(defun f (x) (g 2))
(defun g (y) (+ x y))
> (f 5)
7
Dynamic Binding,which appears to bind to a lexically scoped variable
不,x
被DEFVAR
全局定义为特殊的。因此,f
为 x
创建了一个动态绑定,并在动态环境中查找函数 x
中 g
的值。
开发者的基本规则
- 永远不要使用未定义的变量
- 在使用特殊变量时,始终将
*
放在它们周围,以便在使用它们时始终可见,即正在使用动态绑定和查找。这也确保人们不会NOT 意外地将变量全局声明为special。从那时起,(defvar x 42)
和x
将始终是使用动态绑定的特殊变量。这通常不是我们想要的,并且可能会导致难以调试错误。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。