How do you create local bindings?
Use a let-expression. Here's how to put one together:
First, draw up a binding specification for each variable that you want to bind locally. Each binding specification consists of a left parenthesis, the variable to be bound, an expression, and a right parenthesis. The idea is that, as in a definition, the value of the expression will become the value of the variable -- it will be written on the three-by-five card that is inserted in the box representing the local environment. A typical binding expression looks like this:
Here(seconds-in-week (* 7 24 60 60))
seconds-in-week is the variable and (* 7 24 60
60) is the expression that provides its value.
Next, put a pair of parentheses around all of the binding specifications to form a binding specification list. Even if you're proposing to bind only one variable, you need this extra pair of parentheses so that Scheme will recognize that you want just one.
Now draw up the body of the let-expression, which is a sort of mini-program, a sequence of definitions and commands that are to be processed in the extended environment that includes the local bindings. The body may consist entirely of commands; if there are any definitions, they must come first, before any of the commands. There must be at least one command.
You can now assemble the entire let-expression, as follows: a left
parenthesis, the keyword let, the binding specification list,
the body, and a right parenthesis.
Here's a simple example, with only one local binding and a body comprising no definitions and one command:
This means: Let(let ((number 5)) (* number (+ number 1)))
number be 5 and compute the product of
number and number plus one. Five times six is
thirty, so the value of this expression is 30:
> (let ((number 5))
(* number (+ number 1)))
30
Can you run down the parts inventory on that expression one more time?
The outermost parentheses and the keyword let are the fixed
syntax of the let-expression. ((number 5)) is the binding
specification list; in this case, there's only one binding specification,
(number 5). (* number (+ number 1)) is the body,
in this case consisting just of the one command to compute the value of the
expression. The command is a call to the * procedure with the
arguments number and (+ number 1). This second
argument is a call to the + procedure with the arguments
number and 1.
OK, I guess. Now what's the difference between this and a top-level definition?
The binding that makes 5 the value of number can be examined
only in the body of the let-expression. If the same variable occurs
outside of the let-expression, a different environment, not containing that
binding, is consulted instead. Watch what happens:
> (let ((number 5))
(* number (+ number 1)))
30
> (+ 12 number)
ERROR: unbound variable: number
; in expression: (... number)
; in top level environment.
The variable is unbound in the top-level environment, even though
it is bound in the body of the let-expression. If you used a definition
instead, you'd make an irreversible change in the top-level environment:
What happens if> (define number 5) > (* number (+ number 1)) 30 > (+ 12 number) 17
number is already bound before you introduce
the local binding?
The local binding takes precedence inside the body of the let-expression. The previously established global binding holds both before and after:
> (define number 100)
> number
100
> (let ((number 5))
(* number (+ number 1)))
30
> number
100
Replacing the let-expression in the preceding example to a global definition
highlights the difference between a global and a local binding.
Let's see a more complicated example now. A slightly more complicated example.> (define number 100) > number 100 > (define number 5) > (* number (+ number 1)) 30 > number 5
OK. Suppose you want to calculate the cube of the sum of 181 and 263.
One approach would be to write out a three-operand call to *
and to compute the sum three times to get the three operands:
But it would be better to compute it once, saving the result in a local variable, and then to use that variable as the repeated operand to(* (+ 181 263) (+ 181 263) (+ 181 263))
*:
Show me a let-expression that has more than one binding specification.(let ((sum (+ 181 263))) (* sum sum sum))
Here's one. How much larger is a rectangular solid that measures 12 units by 18 units by 24 units than one that is two units shorter in each dimension? In other words, compute the difference (in cubic units) between the volumes of the two solids.
(let ((length 12)
(width 18)
(height 24))
(- (* length width height)
(* (- length 2) (- width 2) (- height 2))))
Now show me one that has more than one command in its body.
Well, here's a trivial example. It's a variation on the ``Hello, world'' program:
When executed in batch mode, this program generates the output line(let ((sum (+ 5 7))) (display "I am delighted to report that the sum of 5 and 7 is ") (display sum) (display ".") (newline))
In this case, the body of the let-expression consists of four separate commands -- three calls to theI am delighted to report that the sum of 5 and 7 is 12.
display procedure and one to
the newline procedure.
Can you have one let-expression nested inside another -- say, with one being the body of the other?
Yes. The programmer can choose any commands she wants for the body of a let-expression, including a command to evaluate another let-expression. In fact, this comes in handy when you want to use the variable that is bound in the outer let-expression in the computation of the value of the variable bound in the inner let-expression:
> (let ((number 5))
(let ((successor (+ number 1)))
(* number successor)))
30
Couldn't you do the same thing just by putting both binding specifications
in the same binding specification list?
No, that wouldn't work. Let me first show you what happens, and then explain why it happens:
> (let ((number 5)
(successor (+ number 1)))
(* number successor))
+: unbound variable: number
The problem is that the expressions that provide values for the variables
in a binding specification list are all evaluated before any entries are
added to the environment. When Scheme tries to evaluate (+ number
1), it needs a value for number, but the local binding
for number doesn't exist yet.
In other words, the bindings specified in a single let-expression are created simultaneously, not one by one. The reason why the nested let-expression works is that the binding specified in the outer let-expression is in place before any part of the inner let-expression is evaluated.
Next topic
Previous topic
Table of contents
This document is available on the World Wide Web as
http://www.math.grin.edu/~stone/scheme-web/let.html