对这个 "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
这是 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