let
sWe 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
.