[Current] [News] [Glance] [Discussions] [Instructions] [Search] [Links] [Handouts] [Outlines] [Readings] [Labs] [Homeworks] [Quizzes] [Exams] [Examples] [Fall2000.01] [Spring2000]
So far we've seen three ways in which a value can be associated with a name in Scheme:
cons
and
quotient
, are predefined. When DrScheme starts up,
these names are already bound to the procedures they denote.
As you may have noted, it is sometimes inconvenient to have to call a helper procedure when all you want to do is name some values within a procedure.
Scheme provides
let
expressions as an alternative way to create
local bindings. A let
-expression contains a binding
list and a body. The body can be any expression, or sequence of
expressions, to be evaluated with the help of the local name bindings. The
binding list is a pair of structural parentheses enclosing zero or more
binding specifications; a binding specification, in turn, is a pair
of structural parentheses enclosing a name and an expression.
Here's the general form of a let
expression
(let ((name1 exp1) (name2 exp2) ... (namen expn)) body1 body2 ... bodym)
When Scheme encounters a let
-expression, it begins by
evaluating all of the expressions inside its binding specifications. Then
the names in the binding specifications are bound to those values. Next,
the expressions making up the body of the let
-expression are
evaluated, in order. The value of the last expression in the body becomes
the value of the entire let
-expression. Finally, the local
bindings of the names are cancelled. (Names that were unbound before the
let
-expression become unbound again; names that had different
bindings before the let
-expression resume those earlier
bindings.)
Here's an
example of a binding list, taken from a let
-expression in a
real Scheme program:
((next (car source)) (stuff '()))
This binding list contains two binding specifications -- one in which the
value of the expression (car source)
is bound to the name
next
, and the other in which the empty list is bound to the
name stuff
. Notice that binding lists and binding
specifications are not procedure calls; their role in a
let
-expression simply to give names to certain values while
the body of the expression is being evaluated. The outer parentheses in a
binding list are ``structural,'' like the outer parentheses in a
cond
-clause -- they are there to group the pieces of the
binding list together.
Using a let
-expression often simplifies an expression that
contains two or more occurrences of the same subexpression. The programmer
can compute the value of the subexpression just once, bind a name to it,
and then use that name whenever the value is needed again. Sometimes this
speeds things up by avoiding such redundancies as the recomputation of
values.
In other cases, there is
little difference in speed, but the code may be a little clearer. For
instance, here is an alternative definition of the remove-all
procedure.
(define remove-all (lambda (item ls) (if (null? ls) '() (let ((first-element (car ls)) (rest-of-result (remove-all item (cdr ls)))) (cond ((equal? first-element item) rest-of-result) ((pair? first-element) (cons (remove-all item first-element) rest-of-result)) (else (cons first-element rest-of-result)))))))
let*
It is also possible to nest
one let
-expression inside another. One might be tempted to
try to combine the binding lists for the nested
let
-expressions, thus:
;; Combining the binding lists doesn't work! ;; (let ((total (+ 8 3 4 2 7)) (mean (/ total 5))) (* mean mean))
This wouldn't work (try it and see!), and it's important to understand why
not. The problem is that, within one binding list, all of the
expressions are evaluated before any of the names are bound.
Specifically, Scheme will try to evaluate both (+ 8 3 4 2 7)
and (/ total 5)
before binding either of the names
total
and mean
; since (/ total 5)
can't
be computed until total
has a value, an error occurs. You have
to think of the local bindings coming into existence simultaneously rather
than one at a time.
Because one often needs sequential rather than simultaneous binding, Scheme
provides a variant of the let
-expression that rearranges the
order of events: If one writes let*
rather than
let
, each binding specification in the binding list is
completely processed before the next one is taken up:
;; Using LET* instead of LET works! ;; (let* ((total (+ 8 3 4 2 7)) (mean (/ total 5))) (* mean mean))
The star in the keyword let*
has nothing to do with
multiplication. Just think of it as an oddly shaped letter.
One can use a let
- or let*
-expression to create a
local name for a procedure:
(define hypotenuse-of-right-triangle (let ((square (lambda (n) (* n n)))) (lambda (first-leg second-leg) (sqrt (+ (square first-leg) (square second-leg))))))
Regardless of whether square
is defined outside this
definition, the local binding gives it the appropriate meaning within the
lambda
-expression that describes what
hypotenuse-of-right-triangle
does.
February 26, 1997 (John Stone)
March 17, 2000 (John Stone)
2 October 2000 (Samuel A. Rebelsky)
http://www.cs.grinnell.edu/~stone/courses/scheme/local-bindings.xhtml
[Current] [News] [Glance] [Discussions] [Instructions] [Search] [Links] [Handouts] [Outlines] [Readings] [Labs] [Homeworks] [Quizzes] [Exams] [Examples] [Fall2000.01] [Spring2000]
Disclaimer Often, these pages were created "on the fly" with little, if any, proofreading. Any or all of the information on the pages may be incorrect. Please contact me if you notice errors.
This page may be found at http://www.cs.grinnell.edu/~rebelsky/Courses/CS151/2000F/Readings/local-bindings.html
Source text last modified Mon Oct 2 11:41:55 2000.
This page generated on Mon Oct 2 11:44:55 2000 by Siteweaver. Validate this page's HTML.
Contact our webmaster at rebelsky@grinnell.edu