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)