要理解 Scheme 的闭包, 需要先理解求值的环境模型(幸好不是”要理解递归, 你需要先理解递归:D”).
我看到过的对求值环境模型最好的阐述, 还是 SICP 的第 3.2 章, 该书通篇未提 Closure 一词, 然而道理已阐明其中.
现在就用求值环境模型来印证一下闭包的例子:
(define my-counter
(let ((count 0))
(lambda ()
(set! count (+ count 1))
count)))
(my-counter) ; 返回 1
(my-counter) ; 返回 2
(my-counter) ; 返回 3
my-counter
就是闭包. (my-counter)
是如何求值的呢?
首先是把 my-counter
绑定到一个过程体:
(define my-counter
(let ((count 0))
(lambda ()
(set! count (+ count 1))
count)))
Scheme 解释器执行上面的代码时, 会求值 (let ...)
那部分, 而求值的后果就是建立一个框架, 对应于下图的 E1, E1 的外围环境为执行时的全局环境 Global E, 环境变成这样:
+-----------------------+
| |
Global E->| my-counter |
| | |
+-------|---------------+
| ^
| |
| +----------+
| | count: 0 |<--E1
| |__________/
| ^
v |
(.)-(.)-----+
|
v
P: nil
body: (set! count (+ counter 1))
count
从上图可以看出, my-counter
绑定到一个参数为空的过程体, 这个过程体指向的环境 E1 又绑定了一个 count = 0
的变量.
当第一次执行 (my-counter)
这个闭包时, 从 my-counter 这个过程体第一个框架往上搜索 count
这个变量, 并把它修改为原值加一. 执行完毕后框架图变为:
+-----------------------+
| |
Global E->| my-counter |
| | |
+-------|---------------+
| ^
| |
| +----------+
| | count: 1 |<--E1
| |__________/
| ^
v |
(.)-(.)-----+
|
v
P: nil
body: (set! count (+ counter 1))
count
并返回 1
, 第二次执行时会再次修改 E1 框架里的 count, 使它为原值加一.
现在能明白这段闭包定义的含义了吧:
“闭包 = 函数 + 引用环境”.