8.6 KiB
Local Binding
Although internal define
s can be used for local binding, Racket
provides three forms that give the programmer more control over
bindings: let
, let*
, and letrec
.
1. Parallel Binding: let
+[missing] in [missing] also documents
let
.
A let
form binds a set of identifiers, each to the result of some
expression, for use in the let
body:
(let ([id expr] ...) body ...+)
The id
s are bound “in parallel.” That is, no id
is bound in the
right-hand side expr
for any id
, but all are available in the
body
. The id
s must be different from each other.
Examples:
> (let ([me "Bob"])
me)
"Bob"
> (let ([me "Bob"]
[myself "Robert"]
[I "Bobby"])
(list me myself I))
'("Bob" "Robert" "Bobby")
> (let ([me "Bob"]
[me "Robert"])
me)
eval:3:0: let: duplicate identifier
at: me
in: (let ((me "Bob") (me "Robert")) me)
The fact that an id
’s expr
does not see its own binding is often
useful for wrappers that must refer back to the old value:
> (let ([+ (lambda (x y)
(if (string? x)
(string-append x y)
(+ x y)))]) ; use original +
(list (+ 1 2)
(+ "see" "saw")))
'(3 "seesaw")
Occasionally, the parallel nature of let
bindings is convenient for
swapping or rearranging a set of bindings:
> (let ([me "Tarzan"]
[you "Jane"])
(let ([me you]
[you me])
(list me you)))
'("Jane" "Tarzan")
The characterization of let
bindings as “parallel” is not meant to
imply concurrent evaluation. The expr
s are evaluated in order, even
though the bindings are delayed until all expr
s are evaluated.
2. Sequential Binding: let*
+[missing] in [missing] also documents
let*
.
The syntax of let*
is the same as let
:
(let* ([id expr] ...) body ...+)
The difference is that each id
is available for use in later expr
s,
as well as in the body
. Furthermore, the id
s need not be distinct,
and the most recent binding is the visible one.
Examples:
> (let* ([x (list "Burroughs")]
[y (cons "Rice" x)]
[z (cons "Edgar" y)])
(list x y z))
'(("Burroughs") ("Rice" "Burroughs") ("Edgar" "Rice" "Burroughs"))
> (let* ([name (list "Burroughs")]
[name (cons "Rice" name)]
[name (cons "Edgar" name)])
name)
'("Edgar" "Rice" "Burroughs")
In other words, a let*
form is equivalent to nested let
forms, each
with a single binding:
> (let ([name (list "Burroughs")])
(let ([name (cons "Rice" name)])
(let ([name (cons "Edgar" name)])
name)))
'("Edgar" "Rice" "Burroughs")
3. Recursive Binding: letrec
+[missing] in [missing] also documents
letrec
.
The syntax of letrec
is also the same as let
:
(letrec ([id expr] ...) body ...+)
While let
makes its bindings available only in the body
s, and let*
makes its bindings available to any later binding expr
, letrec
makes
its bindings available to all other expr
s—even earlier ones. In other
words, letrec
bindings are recursive.
The expr
s in a letrec
form are most often lambda
forms for
recursive and mutually recursive functions:
> (letrec ([swing
(lambda (t)
(if (eq? (car t) 'tarzan)
(cons 'vine
(cons 'tarzan (cddr t)))
(cons (car t)
(swing (cdr t)))))])
(swing '(vine tarzan vine vine)))
'(vine vine tarzan vine)
> (letrec ([tarzan-near-top-of-tree?
(lambda (name path depth)
(or (equal? name "tarzan")
(and (directory-exists? path)
(tarzan-in-directory? path depth))))]
[tarzan-in-directory?
(lambda (dir depth)
(cond
[(zero? depth) #f]
[else
(ormap
(λ (elem)
(tarzan-near-top-of-tree? (path-element->string elem)
(build-path dir elem)
(- depth 1)))
(directory-list dir))]))])
(tarzan-near-top-of-tree? "tmp"
(find-system-path 'temp-dir)
4))
#f
While the expr
s of a letrec
form are typically lambda
expressions,
they can be any expression. The expressions are evaluated in order, and
after each value is obtained, it is immediately associated with its
corresponding id
. If an id
is referenced before its value is ready,
an error is raised, just as for internal definitions.
> (letrec ([quicksand quicksand])
quicksand)
quicksand: undefined;
cannot use before initialization
4. Named let
A named let
is an iteration and recursion form. It uses the same
syntactic keyword let
as for local binding, but an identifier after
the let
instead of an immediate open parenthesis
triggers a
different parsing.
(let proc-id ([arg-id init-expr] ...)
body ...+)
A named let
form is equivalent to
(letrec ([proc-id (lambda (arg-id ...)
body ...+)])
(proc-id init-expr ...))
That is, a named let
binds a function identifier that is visible only
in the function’s body, and it implicitly calls the function with the
values of some initial expressions.
Examples:
(define (duplicate pos lst)
(let dup ([i 0]
[lst lst])
(cond
[(= i pos) (cons (car lst) lst)]
[else (cons (car lst) (dup (+ i 1) (cdr lst)))])))
> (duplicate 1 (list "apple" "cheese burger!" "banana"))
'("apple" "cheese burger!" "cheese burger!" "banana")
5. Multiple Values: let-values
, let*-values
, letrec-values
+[missing] in [missing] also documents multiple-value binding forms.
In the same way that define-values
binds multiple results in a
definition see \[missing\]
, let-values
, let*-values
, and
letrec-values
bind multiple results locally.
(let-values ([(id ...) expr] ...)
body ...+)
(let*-values ([(id ...) expr] ...)
body ...+)
(letrec-values ([(id ...) expr] ...)
body ...+)
Each expr
must produce as many values as corresponding id
s. The
binding rules are the same for the forms without -values
forms: the
id
s of let-values
are bound only in the body
s, the id
s of
let*-values
s are bound in expr
s of later clauses, and the id
s of
letrec-value
s are bound for all expr
s.
Example:
> (let-values ([(q r) (quotient/remainder 14 3)])
(list q r))
'(4 2)