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?
- 示例1:x,一个无限范围的变量,在fun1中设置为5,因此fun2访问这个值。这是正确的解释吗?
- 示例 2:x 被 defvar 设置为 100,但为什么在调用 fun1 时没有重新设置为 5 ?我认为绑定是在调用函数时发生的,还是在定义函数时发生的?似乎在定义 fun1 时 x 尚未绑定,因此程序的其余部分看不到 fun1 中 x 的绑定(在词法范围内),然后“全局”绑定发生在随后的 defvar。那么函数调用中的行为 x 是由于 fun1 中的词法阴影但没有 fun2 中的动态阴影吗? IE。这里有两个不同的 x 实例,因为 fun1 首先定义了它的 x 并且当时没有看到“全局”x。
- 示例 3:这里似乎是因为 x 是首先全局设置的,所以 fun1 和 fun2 都引用了 x 的同一个实例,因此它的值在 fun1 期间更新并在 fun2 期间也应用(均为 5)?此外,当我最后询问 x 的值时,我得到 100(为什么?当 fun2 返回 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?
主要是,让我详细说明一下。
当x
被defvar
声明时,变量被声明为特殊变量,从现在开始x
总是可见作为特殊变量,动态绑定。当你打电话时:
(fun1 5)
fun1
中的绑定是动态完成的,这意味着 fun1
和 fun2
的 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
被评估时, x
在 fun1
中的绑定仍然是词法的,因此调用 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*
的原因。如果您在某处用 defvar
或 defparameter
定义了一个变量,使用与某处的参数或局部变量相同的变量,您可能会在不知道的情况下临时更改该变量,并且可能很难找到它发生的位置!当人们在参数中看到 *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
这个问题是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?
- 示例1:x,一个无限范围的变量,在fun1中设置为5,因此fun2访问这个值。这是正确的解释吗?
- 示例 2:x 被 defvar 设置为 100,但为什么在调用 fun1 时没有重新设置为 5 ?我认为绑定是在调用函数时发生的,还是在定义函数时发生的?似乎在定义 fun1 时 x 尚未绑定,因此程序的其余部分看不到 fun1 中 x 的绑定(在词法范围内),然后“全局”绑定发生在随后的 defvar。那么函数调用中的行为 x 是由于 fun1 中的词法阴影但没有 fun2 中的动态阴影吗? IE。这里有两个不同的 x 实例,因为 fun1 首先定义了它的 x 并且当时没有看到“全局”x。
- 示例 3:这里似乎是因为 x 是首先全局设置的,所以 fun1 和 fun2 都引用了 x 的同一个实例,因此它的值在 fun1 期间更新并在 fun2 期间也应用(均为 5)?此外,当我最后询问 x 的值时,我得到 100(为什么?当 fun2 返回 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?
主要是,让我详细说明一下。
当x
被defvar
声明时,变量被声明为特殊变量,从现在开始x
总是可见作为特殊变量,动态绑定。当你打电话时:
(fun1 5)
fun1
中的绑定是动态完成的,这意味着 fun1
和 fun2
的 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
被评估时, x
在 fun1
中的绑定仍然是词法的,因此调用 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*
的原因。如果您在某处用 defvar
或 defparameter
定义了一个变量,使用与某处的参数或局部变量相同的变量,您可能会在不知道的情况下临时更改该变量,并且可能很难找到它发生的位置!当人们在参数中看到 *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