Common Lisp 中的动态绑定

Dynamic Binding in Common Lisp

这个问题是Common Lisp scoping (dynamic vs lexical)

的延伸

我已经阅读并(希望)理解 Common Lisp 中范围和范围的概念(link:https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node43.html),但我无法理解以下三个示例。所有示例都是 运行 在 SBCL/Slime/Emacs.

中的新 lisp 会话中

示例 1:打印 5 和 5

(defvar x 100)
(defun fun1 (x)
   (print x)
   (fun2))

(defun fun2 ()
   (print x))
     
(fun1 5)

示例 2:打印 5 和 100

 (defun fun1 (x)
   (print x)
   (fun2))

 (defun fun2 ()
   (print x))
    
 (defvar x 100)
 
 (fun1 5)

示例 3:打印 5 & 5 & 100

(defvar x 100)

(defun fun1 (x)
  (print x)
  (fun2))

(defun fun2 ()
  (print x))

(defvar x 100)
     
(fun1 5)

x

我理解为什么 fun1 总是打印 5(由于词法范围,但如果我错了请更正)。我不明白的是为什么 fun2 在示例 1 中打印 5,在示例 2 中打印 100,在示例 3 中再次打印 5?

它与 Guy Steel 的 Common Lisp 书中的以下摘录有关,但我无法理解它:

"Constructs that use lexical scope effectively generate a new name for each established entity on each execution. Therefore dynamic shadowing cannot occur (though lexical shadowing may). This is of particular importance when dynamic extent is involved."

以下陈述是否总是正确的(来源:https://courses.engr.illinois.edu/cs421/sp2010/lectures/dynamicscope.pdf):

The binding rule in Lisp is this: a use of a name is bound to the most recent declaration of that name that is still live.

我开始了解其中的一些部分,但无法全面了解所有三个部分,所以如果您能提供帮助,那将非常有帮助。

Example 1: x, a variable with indefinite scope, is set to 5 in fun1 and accordingly fun2 access this value. Is this a correct interpretation?

主要是,让我详细说明一下。

xdefvar声明时,变量被声明为特殊变量,从现在开始x总是可见作为特殊变量,动态绑定。当你打电话时:

(fun1 5)

fun1 中的绑定是动态完成的,这意味着 fun1fun2 的 return 值都基于 x 的当前动态绑定].

Example 2: [...] I.e. there are two different instances of x here since fun1 defined its x first and did not see a "global" x at the time.

是的,但并非所有口译员都是如此(请参阅 Sylwester 的回答)。当您定义 fun1 时,x 并不知道是特殊的;这意味着此时参数 x 的范围是词法的。稍后,当 defvar 被评估时, xfun1 中的绑定仍然是词法的,因此调用 fun1 不会修改全局变量 [=11 的动态绑定=].

Example 3: [...] Furthermore I get 100 when I ask for the value of x at the end (why? when fun2 is returning 5?

特殊变量具有无限范围,它们随处可见,但它们的绑定具有动态范围,这意味着绑定仅存在于只要建立它的形式。

在这里,当您在顶层请求 x 时,您拥有全局绑定到 x, 100 的值; 5 的值仅在对 fun1 的调用生效时临时绑定到 x

如果您使用 SETF 改变绑定,那么您可以改变全局绑定,但这不是在函数应用或 let 绑定期间发生的情况。

您的技巧在不同的实现中有不同的作用。例如。在 CLISP 中,它不会动态编译它们的函数,在前两个示例中的行为将完全相同,如果您在运行时编译函数,则与您的输出完全相同。

动态作用域意味着词法作用域不适用:

(defparameter *test* 100)

(defun print-test ()
  (print *test*))

(defun call-print-test-with (*test*)
  (print-test))

(print-test)         ; prints 100
(call-print-test 10) ; prints 10

因为*test*是动态的(全局的)改变一个同名的局部变量会临时覆盖它,直到覆盖它的作用域消失。这就是动态的意思。

如果 *test* 是词法范围的,两者都会打印 100.

这就是为什么您应该始终对全局变量使用 *earmuffs* 的原因。如果您在某处用 defvardefparameter 定义了一个变量,使用与某处的参数或局部变量相同的变量,您可能会在不知道的情况下临时更改该变量,并且可能很难找到它发生的位置!当人们在参数中看到 *earmuffs*let 时,他们就会明白这是意图。例如

(with-output-to-string (*standard-output*)
  (some-function-whose-printed-output-you-want))
; ==> a string with the actual output

被调用的函数不是最明智的。它认为它正在打印到 stdout 但你已经包装它并在它执行期间更改了输出流。

您的代码的一些注释:

示例 1

(defvar x 100)    ; declares X to be special, globally and locally
                  ; also sets X to 100

(defun fun1 (x)   ; X is a dynamically bound variable
  (print x)       ; lookup of dynamic binding of X
  (fun2))        

(defun fun2 ()
  (print x))      ; lookup of dynamic binding of X
     
(fun1 5)

示例 2

 (defun fun1 (x)  ; X is a lexical local variable
   (print x)      ; lexical reference to X
   (fun2))

 (defun fun2 ()
   (print x))     ; X is undeclared/undefined
                  ; the exact behaviour is undefined in Common Lisp
                  ; many implementations assume dynamic lookup of X
                  ; most compilers will show a warning
                  ; CMUCL also by default declared X globally to be special
                  ; -> don't use this in your code
    
 (defvar x 100)   ; declares X to be special, globally and locally 
                  ; also sets X to 100     
 (fun1 5)

示例 3

(defvar x 100)    ; declares X to be special, globally and locally
                  ; also sets X to 100

(defun fun1 (x)   ; X is a dynamically bound variable
  (print x)       ; lookup of dynamic binding of X
  (fun2))

(defun fun2 ()
  (print x))      ; lookup of dynamic binding of X

(defvar x 100)    ; does nothing
                  ;  -> X is already declared special
                  ;  -> X already has a value
                  ;     see also: DEFPARAMETER
     
(fun1 5)

x                 ; lookup of global (or thread local) value of X