挡住了去路的,往往不是大树,而是小藤 —— 于渊
疑问
在复习《On Lisp》闭包时,偶然发现一些以前没有注意到的现象:
我的 lisp 实现是 SBCL 1.3.2
以下是书中闭包的例子:
(let ((y 7))
(defun scope-test (x)
(list x y)))
编译该函数,如书中所说
(scope-test 3)
(let ((y 5)) (scope-test 3))
的结果都是:(3 7)。
但如果我先定义了全局变量 y
(defvar y 10)
然后再编译该闭包,
(scope-test 3)
的结果变成了 (3 10);
(let ((y 5)) (scope-test 3))
的结果变成了:(3 5)。
这当然和 SBCL 对自由变量 y 的处理有关。但是不太清楚背后的逻辑。
解释
第1种情况
在定义这个闭包时,y 处于 let 创建的词法空间内,y 是词法作用域(scope),动态生命周期(extent)。defun 创建(establishing)的 scope-test 函数处于 let 创建的词法空间内。
所以在执行 (scope-test 3) 时,变量 y 先从 scope-test 执行时的词法空间里找值绑定,但是没有找到,然后在 scope-test 函数创建的词法空间里找到了 7 这个绑定,所以结果为 (3 7)。
同理,虽然 (let ((y 5)) (scope-test 3)) 里的 let 又创建了一个词法空间。在执行这个表达式时,y 的值绑定先找到的是定义 scope-test 函数时的变量绑定 7。 所以,结果是 (3 7)。
第2种情况
如果我用 defvar 定义了一个 special 变量 y。y 是动态作用域(不定(indefinite)作用域,动态生命周期)。
在执行 (scope-test 3) 时,变量 y 先从当前栈空间去找值绑定,但是没有找到,然后在调用栈的空间里去找,找到了 7 这个值,所以结果为 (3 7)。
在执行 (let ((y 5)) (scope-test 3)) 时,变量 y 先从当前栈空间去找值绑定,但是没有找到,然后在由 let 所处的栈空间去找,找到了 5 这个值,所以结果为 (3 5)。
