let*
let
is useful for most local variables, but sometimes you want
to create several local variables in sequence, with each variable's
value available to compute the next variable's value.
For example, it is common to "destructure" a data structure, extracting part of the structure, then a part of that part, and so on. We could do this by simply nesting expressions that extract parts, but then we don't have understandable names for the intermediate results of the nested expressions.
(In other cases, we may want to do more than one thing with the results of one of the nested expressions, so we need to create a variable so that we can refer to it in more than one body expression.)
Consider the code fragment:
(let ((a-structure (some-procedure))) (let ((a-substructure (get-some-subpart a-structure))) (let ((a-subsubstructure (get-another-subpart a-substructure))) (foo a-substructure))))
Scheme provides a convenient syntax for this sort of nested let;
can be written as a single let*
(let* ((a-structure (some-procedure)) (a-substructure (get-some-subpart a-structure)) (a-subsubstructure (get-another-subpart a-substructure))) (foo a-substructure))))
Notice that this wouldn't work if we wrote it as a normal let
that binds three variables. A block structure diagram shows why:
(let ((a-structure (some-procedure)) (a-substructure (get-some-subpart a-structure)) (a-subsubstructure (get-another-subpart a-substructure))) +---------------------------------------------------------------+ | (foo a-substructure) ; scope of all three variables | ))) +---------------------------------------------------------------+
Now we see that all of the initial value expressions for the let
variables
are outside the scope of any of the variables. a-substructure
and
a-substructure
will not refer to the bindings introduced by this
let
, but to whatever bindings (if any) are visible outside the
let
.
With let*
, it's different:
(let* ((a-structure (some-procedure)) +-------------------------------------------------------------------+ | (a-substructure (get-some-subpart a-structure)) | +----------------------------------------------------------------+ | | (a-subsubstructure (get-another-subpart a-substructure))) | | +-------------------------------------------------------------+ | | | (foo a-subsubtructure) | | | ))) +-------------------------------------------------------------+--+--+
Each initial value clause is in the scope of the previous variable in
the let*
. From the nesting of the boxes, we can see that bindings
become visible one at a time, so that the value of a binding can be
used in computing the initial value of the next binding.
There's another local binding construct in Scheme, letrec
, which
is used when creating mutually recursive local procedures. I'll discuss
that later, when I describe how local procedures work in Scheme.
================================================================== This is the end of Hunk I TIME TO TRY IT OUT At this point, you should go read Hunk J of the next chapter and work through the examples using a running Scheme system. Then return here and resume this chapter. ==================================================================
Go to Hunk J, which starts at section Local Variables, let
, and Lexical Scope (Hunk J).