对这个 "oop under-the-hood" 计数器示例的工作原理感到非常困惑

extremely confused about how this "oop under-the-hood" example of a counter works

这是 make-counter 过程和对它的调用

(define make-counter
    (let ((glob 0))
        (lambda ()
            (let ((loc 0))
                (lambda ()
                    (set! loc (+ loc 1))
                    (set! glob (+ glob 1))
                    (list loc glob))))))

> (define counter1 (make-counter))
 counter1

> (define counter2 (make-counter))
 counter2

> (counter1)
(1 1)

> (counter1)
(2 2)

> (counter2)
(1 3)

> (counter1)
(3 4)

我不明白为什么 glob 表现为 class 变量,而 loc 表现为实例变量。

当代码的每个部分都是 运行 时,可能最容易考虑。你评价

(define make-counter (let ((0 glob)) ...))

只计算一次,所以 let 只计算一次。这意味着只有 一个 绑定,并且它的值由 let 主体中的所有内容共享。现在,let 的主体是什么?这是一个 lambda 函数,它成为 make-counter:

的值
(lambda ()          ; this function is the value of make-counter

  (let ((loc 0))    ; so this stuff gets execute *each time* that
    (lambda ()      ; make-counter is called
      ...           ;
      )))           ;

现在,每次你调用 make-counter,你计算(let ((loc 0)) (lambda () …)),它创建一个新绑定和 returns 一个可以访问它的 lambda 函数(以及从外部访问全局绑定。

因此调用 make-counter 的每个结果都可以访问 single 绑定 glob,以及访问 loc.

的每个结果绑定

让我们检查一下程序:

(define make-counter
  (let ((g 0))
    (lambda ()
      (let ((l 0))
        (lambda ()
          (set! l (+ l 1))
          (set! g (+ g 1))
          (list l g))))))

该程序说明了抽象(lambda-表达式)如何创建 包含对自由变量的引用的闭包。

明确地查看和检查自由变量会很有帮助, 所以让我们假设我们想 运行 上面的程序用一种语言 不支持 lambda。换句话说,让我们尝试重写 将程序转换为使用更简单结构的程序。

首先是摆脱分配。让我们分配一个盒子 (认为​​长度为一的向量)可以保存一个值。 然后,赋值可以使用 set-box! 更改 box 包含的值。

; Assignment conversion: Replace assignable variables with boxes.
; The variables l and g are both assigned to

 (define make-counter
   (let ((g (box 0)))
     (lambda ()
       (let ((l (box 0)))
         (lambda ()
           (set-box! l (+ (unbox l) 1))
           (set-box! g (+ (unbox g) 1))
           (list (unbox l) (unbox g)))))))

此程序等同于原始程序(试一试!)。

下一步是用自由变量注释每个 lambda:

(define make-counter
  (let ((g (box 0)))
    (lambda ()           ; g is free in lambda1
      (let ((l (box 0)))
        (lambda ()       ; g and l are free lambda2
          (set-box! l (+ (unbox l) 1))
          (set-box! g (+ (unbox g) 1))
          (list (unbox l) (unbox g)))))))

现在我们准备用显式闭包替换 lambda。 闭包持有
i) 没有自由变量的函数 ii) 创建闭包时自由变量的值

我们将使用向量来存储 i) 和 ii)。

(define (make-closure code . free-variables)
  (apply vector code free-variables))

我们可以像这样得到没有自由变量的函数:

(define (closure-code closure)
  (vector-ref closure 0))

我们可以像这样得到第 i 个自由变量:

(define (closure-ref closure i)
  (vector-ref closure (+ i 1)))

要应用闭包,请调用没有自由变量的函数(代码) 与闭包(哪些代码需要找到 自由变量)和实际参数。

(define (apply-closure closure . args)
  (apply (closure-code closure) closure args))

下面是lambda1

对应的代码
(define (lambda1 cl) ; cl = (vector lambda1 g)
  (let ((g (closure-ref cl 0))) ; g is the first free variable of lambda1
    (let ((l (box 0)))
      (make-closure lambda2 g l))))

由于 lambda1 是一个没有参数的函数,唯一的输入是闭包。 它做的第一件事是检索自由值 g.

注意 lambda1 returns 一个闭包:(make-closure lambda2 g l) 在这里我们看到当 lambda2 的闭包是 g 和 l 的值时 被保留。

现在 lambda2:

(define (lambda2 cl) ; cl = (vector lambda2 g l)
  (let ((g (closure-ref cl 0))
        (l (closure-ref cl 1)))
    (set-box! l (+ (unbox l) 1))
    (set-box! g (+ (unbox g) 1))
    (list (unbox l) (unbox g))))

最后生成一个 lambda1 闭包的计数器:

(define make-counter (make-closure lambda1 (box 0)))

我们现在可以看到我们的程序在运行了:

(define counter1 (apply-closure make-counter))
counter1
(define counter2 (apply-closure make-counter))
counter2

(apply-closure counter1)
(apply-closure counter1)
(apply-closure counter2)
(apply-closure counter1)

输出为:

'#(#<procedure:lambda2> #&0 #&0)
'#(#<procedure:lambda2> #&0 #&0)
'(1 1)
'(2 2)
'(1 3)
'(3 4)

这意味着程序的运行方式与原始程序相同。 但是现在我们可以检查两个计数器的自由变量:

> counter1
'#(#<procedure:lambda2> #&4 #&3)
> counter2
'#(#<procedure:lambda2> #&4 #&1)

我们可以检查两个计数器是否共享相同的 g:

> (eq? (closure-ref counter1 0)
       (closure-ref counter2 0))
#t

我们还可以检查它们是否有两个包含 l 的不同框。

> (eq? (closure-ref counter1 1)
   (closure-ref counter2 1))

#f