schemeletsicp

How to make `set!` change the variable in `let` (Scheme)?


Recently when I self-learnt MIT 6.5151 course, I have read SICP 1 to 2.1 as ps0 requires (also read 2.2.1 as CS 61A notes requires) and then Software Design for Flexibility (SDF) Prologue, chapter 1 and partly Appendix on Scheme.

I encountered this problem when reading "Appendix on Scheme":

For example, we can use set! to make a device that increments a count every time we call it:

(define (make-counter)
        (let ((count 0))
            (lambda ()
              (set! count (+ count 1))
              count)))

Let's make two counters:

(define c1 (make-counter))
(define c2 (make-counter))

These two counters have independent local state.

This is one similar question to this but uses (count) instead of count:

(define (count)
  (let ((cont 0))
    (lambda ()
      (set! cont (+ cont 1))
      cont)))

Then do the following in MIT-Scheme:

1 ]=> ((count))

;Value: 1

1 ]=> ((count))

;Value: 1

Could someone tell me the difference between this and the original define count version? Does it due to the above one will make one new instance lambda for each call of ((make-counter)) while the original one doesn't? (Better with one reference of SICP chapters not read up to now.)


After I read SICP chapter 3.2, I understood this problem deeper. (count) is similar to (make-withdraw 100) and count is same as count in CS61A notes p51. Each (count) will create one new env as the book says

A procedure object is applied to a set of arguments by constructing a frame, binding the formal parameters of the procedure to the arguments of the call, and then evaluating the body of the procedure in the context of the new environment constructed.


Solution

  • Your count function

    (define (count)
      (let ((cont 0))
        (lambda ()
          (set! cont (+ cont 1))
          cont)))
    

    is the same as the make-counter function you also show, just with different variable names. count is a function that returns a function; calling that one increments a counter. Each returned function has its own unique copy of the cont variable in its lexical environment. If you do

    (define c1 (count))
    (define c2 (count))
    

    then c1 and c2 have their own individual internal counter variables. ((count)) first executes (count), returning a new function, and then executes it, returning what it returns. That new function then gets garbage collected at some point because there's nothing with a reference to it keeping it alive; it can only be called that one time.

    The definition in the linked question is

    (define count
      (let ((cont 0))
        (lambda ()
          (set! cont (+ cont 1))
          cont)))
    

    which defines count to the value returned by

    (let ((cont 0))
      (lambda ()
        (set! cont (+ cont 1))
        cont))
    

    which is a function. So every time you invoke this version of count, the function increments the cont variable in its environment. It's equivalent to

    (define count (make-counter))