Very confused about how this "fail under the hood" example works

here is the procedure make-counter

and calls to it

(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)

      

I don't understand why it glob

behaves like a class variable but loc

behaves like an instance variable.

+3


source to share


2 answers


This may be easiest to consider when you execute each piece of code. Do you rate

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

      

only once, so let is evaluated only once. This means that there is only one binding, and its value is shared by the entire body of the let . Now, what's in the body of let ? This is a lambda function that becomes the make-counter value :



(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
      ...           ;
      )))           ;

      

Now, every time you call make-counter , you evaluate (let ((loc 0)) (lambda () & hellip;)) , which creates a new binding and returns the lambda function that has access to it (as well as the global binding outside.

Thus, every result of a call to make-counter has access to a single glob binding , as well as access to a loc result binding .

+6


source


Consider the program:

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

      

The program shows how to create an abstraction ( lambda

-expression) closure containing references to free variables.

It would be helpful to see and check free variables explicitly, so let's assume we want to run the program above in a language it doesn't support lambda

. In other words, try rewriting the program into one that uses simpler constructs.

First, to get rid of assignments. Let's highlight a box (think of it as a vector of length one) that can hold one value. Then an assignment can change the value that the field has with 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)))))))

      

This program is equivalent to the original (try it!).

The next step is to annotate each lambda with your free variables:

(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)))))))

      

We are now ready to replace lambda

with explicit closures. The closure takes place i) a function with no free variables ii) the value of a free variable during the creation of the closure

We will use a vector to store i) and ii).

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

      

We can get a function without such free variables:

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

      

And we can make this free variable like this:

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

      

To apply a closure, you call a function with no free variables (code) with both the closure (which code will have to find the values โ€‹โ€‹of the free variables) and the actual arguments.

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

      



Here is the code corresponding to 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))))

      

Since lambda1 was a function with no arguments, the only input is a closure. The first thing it does is get a free g value.

Note that lambda1 returns a closure: (make-clos lambda2 gl) Here we see that when lambda2 is closed, the values โ€‹โ€‹of g and l are preserved.

Now 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))))

      

Finally, make-counter, which just makes a lambda closure:

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

      

We are now ready to see our program in action:

(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)

      

Output:

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

      

This means that the program works the same as the original. Now, however, we can consider the free variables of the two counters:

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

      

We can check that two counters have the same g:

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

      

We can also check that they have two different blocks containing l.

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

#f

      

+2


source







All Articles