[comp.lang.scheme] Counter in SIOD

ericco@stew.ssl.berkeley.edu (08/15/90)

I typed in the following into my freshly compiled version 2.4 of SIOD:

	> (define (cnt x) (lambda () (let ((y x)) (set! y (+ y 1)))))
	> (set! cnt1 (cnt 10))
	> (cnt1)
	11
	> (cnt1)
	11

Did I miss something in the recent discussion of this feature of
scheme?  Why doesn't the counter 'y' get incremented each on each call
to the cnt1?  Please send me mail, I know this has already been
discussed here.

Thanks,
Eric

Eric
ericco@ssl.berkeley.edu

shaff@RPAL.COM (Mike Shaff) (08/15/90)

ciao,

ericco@ssl.berkeley.edu wrote:

	(define (cnt x) (lambda () (let ((y x)) (set! y (+ y 1)))))

Eric is looking for a function that saves state with respect to y (actually he
probably has something totally different in mind, but he did not want to
complicate things).  There are two problems with this code.  First, you are not
spawning a new closure (combination of state and lexical body).  Second the use
of set! to return a result leads to unspecified execution.  Below is an example
of how to obtain the functionality you want.

(define spawn-counter
  (lambda (x)				;Take in our starting point
    (let ((y x))			;capture it
      (lambda ()			;return a new  closure that will...
	(set! y (+ y 1))		;bang on state each time called
	y))))				;and return the new status
;Value: spawn-counter

(define counter (spawn-counter 10))
;Value: counter

(counter)
;Value: 11

(counter)
;Value: 12

Enjoy, anonymous closures are one of Scheme's most wonderful things.  The other
things?  Macros?  No, just dreaming...

	mas

Once in a while you get shown the light
in the strangest of places if you look at it right.

thompson@CEBAF4.CEBAF.GOV (Al Thompson) (08/16/90)

ciao,

I'm sorry Mike, but I just have to ask: where does a Scheme meister like
yourself acquire such knowledge. I am an Official Scheme Pundit, having
acquired the implementation from MIT, and I would like to have a book to
guide me to novice level as I experiment with the language. A professor
of mine told me that there were textbooks available on Scheme and that 
some computer science departments were even considering offering Scheme
as an intro level course (!). Any suggestions?

Warmest Wishes For A Happy New Year,
Your Friend and Good Buddy, 

Alfred Thompson
Summer Intern (thompson@cebaf4.cebaf.gov)
CEBAF Computer Center
Work Phone: 249-7041

lyn@ZURICH.AI.MIT.EDU (Franklyn Turbak) (08/16/90)

Two recent messages have dealt with the definition of counters in Scheme.
This message attempts to elucidate some problems with these 
definitions.

ericco@ssl.berkeley.edu made the following (incorrect) attempt:

>    > (define (cnt x) (lambda () (let ((y x)) (set! y (+ y 1)))))
>    > (set! cnt1 (cnt 10))
>    > (cnt1)
>    > 11
>    > (cnt1)
>    > 11

Mike Schaff <shaff@rpal.com> responded with the following comments and code:

>    Eric is looking for a function that saves state with respect to y (actually he
>    probably has something totally different in mind, but he did not want to
>    complicate things).  There are two problems with this code.  First, you are not
>    spawning a new closure (combination of state and lexical body).  Second the use
>    of set! to return a result leads to unspecified execution.  Below is an example
>    of how to obtain the functionality you want.
>
>    (define spawn-counter
>      (lambda (x)                      ;Take in our starting point
>        (let ((y x))                   ;capture it
>          (lambda ()                   ;return a new  closure that will...
>            (set! y (+ y 1))           ;bang on state each time called
>            y))))                      ;and return the new status
>    ;Value: spawn-counter
>
>    (define counter (spawn-counter 10))
>    ;Value: counter
>
>    (counter)
>    ;Value: 11
>
>    (counter)
>    ;Value: 12

Mike's note claims that a problem with Eric's code is that it does not
spawn a new closure.  This is incorrect; Eric's call (cnt 10) evaluates
the expression (lambda () (let ((y x)) ...)) which does indeed spawn 
a new closure.  The *real* problem with Eric's code, (in addition to 
the fact that you can't rely on the returned value of SET!, as noted
by Mike) is that each time CNT1 is called, the (let ((y x)) ...) creates
a new frame where Y is bound to the value originally handed to CNT when 
CNT1 was created -- that is, the unchanging value of X, which is 10
in Eric's example.  

Also, both Eric's and Mike's programs contain the spurious variable Y.
The following is a more concise implementation of the counter that 
works in any R^3RS Scheme:

    (define (make-counter x)
      (lambda ()
        (set! x (+ x 1))
        x))
    ;Value: make-counter

    (define counter (make-counter 10))
    ;Value: counter

    (counter)
    ;Value: 11

    (counter)
    ;Value: 12

The distinction between these three programs is clarified by using environment 
diagrams a la Abelson and Sussman (see their "The Structure and Interpretation 
of Computer Programs"). The diagram for Eric's code looks like:

     E0+------------+
       |    CNT-----|--->[Closure
       |            |      Code: (lambda (x) 
       |            |              (lambda () 
       |            |                (let ((y x))
       |            |                  (set! y (+ y 1)))))
       |            |      Env: --+  ]
       |            |             |
       |            |<------------+
       |            |
       |    CNT1----|--->[Closure
       +------------+      Code: (lambda ()
	     ^                     (let ((y x))
	     |                       (set! y (+ y 1))))
     E1+------------+      Env: --+ ]
       |            |             |
       |     X------|---> 10      |
       |            |             |
       |            |<------------+
       +------------+
       ^            ^
       |            |
   E2+---+      E4+---+
     |   |        |   |
     +---+        +---+
       ^            ^
       |            |
   E3+---+      E5+---+
     | Y-|-x->10  | Y-|-x->10
     |  \|        |  \|
     |   +--->11  |   +-->11
     +---+        +---+

Note that CNT1 is bound in environment E0 to a closure whose environment
is E1, a receptacle of local state for the value of X.  The first evaluation
of (CNT1) creates environments E2 (empty because of the nullary call)
and E3, which initially contains a binding of Y to 10.  Later, the value of 
Y is changed to 11 (the notation -x->10 indicates an old binding, invalidated
by set!; the new binding after the set! is notated --->11). The second 
evaluation of (CNT1) creates separate environments E4 and E5, whose "shape"
is the same as that of E2 and E3 (this shape is, in fact, dictated by the
lexical nesting of LAMBDAs and LETs within the definition of CNT !).  
Whether 10 or 11 (or some other value) is returned depends on the vagaries 
of the particular implementation of Scheme you use.

Mike's fix does work, by creating the following environment structure:

     E0+---------------+
       | SPAWN-COUNTER |--->[Closure
       |               |      Code: (lambda (x) 
       |               |              (let ((y x))
       |               |                (lambda ()
       |               |                  (set! y (+ y 1))
       |               |                  y)))
       |               |      Env: --+  ]
       |               |             |
       |               |<------------+
       |               |
       |   COUNTER-----|--->[Closure
       +---------------+      Code: (lambda ()
	     ^                        (let ((y x))
             |                          (set! y (+ y 1))
             |                          y))
     E1+------------+      Env: --+ ]
       |            |             |
       |     X------|---> 10      |
       |            |             |
       +------------+             |
             ^                    |
             |                    |
     E2+------------+             |
       |            |<------------+
       |            |  
       |     Y------|-x-> 10      
       |      \     |             
       |       +----|-x-> 11
       |        \   |
       |         +--|---> 12
       +------------+
       ^            ^
     E3|          E4|
     +---+        +---+
     |   |        |   |
     +---+        +---+

Here, COUNTER is bound to a closure whose environment is E2, the
frame where the binding for Y exists.  This Y is shared by both calls 
to (COUNTER), which explains the appropriate counting behavior.
E3 and E4 are empty frames created by calling the nullary COUNTER.

The environment structures created by my code are similar to 
those created by Mike's, except that there is no frame holding
a binding for Y:

     E0+---------------+
       |  MAKE-COUNTER |--->[Closure
       |               |      Code: (lambda (x) 
       |               |              (lambda ()
       |               |                (set! x (+ x 1))
       |               |                x))
       |               |      Env: --+  ]
       |               |             |
       |               |<------------+
       |               |
       |   COUNTER-----|--->[Closure
       +---------------+      Code: (lambda ()
	     ^                        (set! x (+ x 1))
             |                        x)
             |                Env: --+ ]
     E1+------------+                |
       |            |<---------------+
       |            |  
       |     X------|-x-> 10      
       |      \     |             
       |       +----|-x-> 11
       |        \   |
       |         +--|---> 12
       +------------+
       ^            ^
     E2|          E3|
     +---+        +---+
     |   |        |   |
     +---+        +---+

I hope this clarifies matters!

  Respectfully,

  - Lyn -
  (a.k.a. Captain Abstraction)