Go to the first, previous, next, last section, table of contents.

Block Structure Diagrams for lets

We can make environments and contours clearer by drawing a block diagram showing where the different variables are visible:

(let ((x 10)    ; bindings of x
      (a 20))   ; and a 
 | (foo x)                                 scope of outer x |
 | (let ((x (bar))                           and a          |
 |       (b (baz x x)))                                     |
 |  +------------------------------------------------+      |
 |  | (quux x a)                    scope of inner x |      |
 |  | (quux y b)                    and b            | )    |
 |  +------------------------------------------------+      |
 | (baz x a)                                                |
 | (baz x b)                                                | )

(This kind of block diagram is the origin of the term "block structure.")

Each box represents a contour: it shows where in the program each variable binding will be visible.

We can interpret a block structure diagram by looking outward from an occurrence of a variable name, and using the nearest enclosing box that corresponds to a binding of that name. Now we can see that the final call (baz x b) does not refer to the let variable b---it's not inside the box corresponding to that variable. We can also see that the occurrence of x in that expression refers to the outer x. The occurrence of x in the calls to quux refer to the inner x, because they're inside its box, and inner definitions shadow outer ones.

There's something a little tricky to notice here. When we evaluate the initial value expressions for the inner let, the inner bindings are not visible yet. x still refers to the outer binding of x, not the inner one that we are about to create. Sometimes this is exactly what you want, but sometimes it's not. Because it isn't always what you want, Scheme provides two variants of let, called let* and letrec.

Go to the first, previous, next, last section, table of contents.