Does re-assigning a non-function to be a function with set! create a new frame?

Viewed 54

The following code is confusing me:

(define (even-or-odd-letrec x)
  ((lambda (internal-even? internal-odd?)
     (set! internal-even? (lambda (n)
                (if (= n 0) 'even
                    (internal-odd? (- n 1)))))
     (set! internal-odd? (lambda (n)
               (if (= n 0) 'odd
                   (internal-even? (- n 1)))))
     (internal-even? x))
   #f #f))

As I read it, the environments are as follows:

  1. Inside the environment of even-or-odd-letrec, internal-even? and internal-odd? are initially bound to #f. Their parent environment is the global environment.
  2. set! then changes these two values to the obvious lambdas, but does not change the environments.
  3. Because the environments haven't changed, any calls to internal-even? will look in the global environment for internal-odd? and find nothing.

So how does this code work?

3 Answers

Answering the title of your question: no, setting does not create any new frames.

Setting sets the value of a binding in the current environment frame. Whatever the old and new values might be. Function or no, makes no difference.

Again, set! does not change values. It changes bindings' values. A binding is a pairing of a name and its value. Set!ting changes that name's bound value.

Seen as a named pointer, after being re-set!, a name points to a new value -- that value which was supplied to set! as the second argument.

So contrary to what you say, set! does change the current environment frame -- it mutates its binding for the given name that is sets.

  +---------------+                  +---------------+
  |  n1 ---> v1   |                  |  n1 ---> x    |
  |  n2 ---> v2   |   (set! n1 x)    |  n2 ---> v2   | 
  |---------------|  ------------->  |---------------| 
  |  ..code...    |                  |  ..code...    |
  |_______________|                  |_______________|

"Any call to internal-even? will" ..... stop! this isn't the right way to look at it.

Any reference to internal-even? inside the lambda-created frame will be resolved by looking up the value under the name internal-even? in that same frame, and all will work out fine.

When you (set! internal-even? (lambda (...) ... internal-odd? ...)), it is true that internal-odd?'s value still isn't what it should be, but that's OK, because its value isn't looked up yet -- because internal-even? is not yet run either. When a lambda expression is evaluated (here, as part of the set! call), its value is the function which says, when time comes, when this function runs, then look up the value of any name in it as it is needed.

When applied to two argument values, (lambda (internal-even? internal-odd?) ...) creates a new environment with bindings for internal-even? and internal-odd?. Anything in that environment which refers to internal-even? or internal-odd? will refer to the current value of those bindings at the time when the code runs. set! mutates this environment by altering the values of the bindings, such that the current values of those bindings are now two procedures which themselves refer to the current values of those bindings.


By far the easiest way to understand this, I think, is to implement a little interpreter. It is extremely easy to do this: you use alists for environments, the evaluator needs pretty much to handle if and lambda and set! as special cases. Everyone should write one of these.

Setting a variable does not bind a new location. Note, however, that binding a new variable does not mean to create/enlarge a frame. It is possible (like in C) to keep variables into registers, not in frames, or not keeping them at all if you can prove things about a variable's behavior.

Related