闭包的概念

12 Jul 2015

要理解 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, 使它为原值加一.

现在能明白这段闭包定义的含义了吧:

“闭包 = 函数 + 引用环境”.