非常混淆这个" oop under-the-hood"计数器的例子

时间:2015-07-30 20:12:55

标签: oop functional-programming scheme lisp racket

这里是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表现为类变量,而loc表现为实例变量。

2 个答案:

答案 0 :(得分:6)

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

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

只需一次,所以只评估一次。这意味着只有一个绑定,其值由 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
      ...           ;
      )))           ;

现在,每次调用 生成计数器时,您评估(let((loc 0))(lambda()...)),它创建一个新的绑定并返回一个可以访问它的lambda函数(以及来自外部的全局绑定。

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

答案 1 :(得分:2)

让我们检查一下这个程序:

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

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

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

首先是摆脱任务。我们分配一个盒子 (认为​​长度为1的向量)可以容纳一个值。 然后,赋值可以使用set-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))

我们可以像这样自由变量:

(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返回一个闭包:( 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))))

最后make-counter简单地创建了一个lambda1-closure:

(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