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

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).


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