On this page:
4.6.4Named let
top
up

4.6Local BindingπŸ”— i

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 .

4.6.1Parallel Binding: let πŸ”— i

+Local Binding: let, let*, letrec, ... in The Racket Reference 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 ([idexpr]...)body...+)

The ids 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 ids must be different from each other.

Examples:
> (let ([me"Bob"])
me)

"Bob"

> (let ([me"Bob"]
[myself"Robert"]
[I"Bobby"])
(list memyselfI))

'("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 (xy)
(if (string? x)
(+ xy)))]);use original +
(list (+ 12)
(+ "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 ([meyou]
[youme])
(list meyou)))

'("Jane" "Tarzan")

The characterization of let bindings as “parallel” is not meant to imply concurrent evaluation. The exprs are evaluated in order, even though the bindings are delayed until all exprs are evaluated.

4.6.2Sequential Binding: let* πŸ”— i

+Local Binding: let, let*, letrec, ... in The Racket Reference also documents let* .

The syntax of let* is the same as let :

(let* ([idexpr]...)body...+)

The difference is that each id is available for use in later exprs, as well as in the body. Furthermore, the ids 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 xyz))

'(("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")

4.6.3Recursive Binding: letrec πŸ”— i

+Local Binding: let, let*, letrec, ... in The Racket Reference also documents letrec .

The syntax of letrec is also the same as let :

(letrec ([idexpr]...)body...+)

While let makes its bindings available only in the bodys, and let* makes its bindings available to any later binding expr, letrec makes its bindings available to all other exprs—even earlier ones. In other words, letrec bindings are recursive.

The exprs 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'(vinetarzanvinevine)))

'(vine vine tarzan vine)

> (letrec ([tarzan-near-top-of-tree?
(lambda (namepathdepth)
(or (equal? name"tarzan")
(tarzan-in-directory?pathdepth))))]
[tarzan-in-directory?
(lambda (dirdepth)
(cond
[(zero? depth)#f]
[else
(λ (elem)
(tarzan-near-top-of-tree?(path-element->string elem)
(build-path direlem)
(- depth1)))
(directory-list dir))]))])
(tarzan-near-top-of-tree?"tmp"
(find-system-path 'temp-dir)
4))

directory-list: could not open directory

path: /var/tmp/systemd-private-6ece068a469546499f5d592c840

5efe0-systemd-logind.service-6XAejJ

system error: Permission denied; errno=13

While the exprs 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 ([quicksandquicksand])
quicksand)

quicksand: undefined;

cannot use before initialization

4.6.4Named let πŸ”— i

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-idinit-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 (duplicateposlst)
(let dup([i0]
[lstlst])
(cond
[(= ipos)(cons (car lst)lst)]
[else (cons (car lst)(dup(+ i1)(cdr lst)))])))
> (duplicate1(list "apple""cheese burger!""banana"))

'("apple" "cheese burger!" "cheese burger!" "banana")

4.6.5Multiple Values: let-values , let*-values , letrec-values πŸ”— i

+Local Binding: let, let*, letrec, ... in The Racket Reference also documents multiple-value binding forms.

In the same way that define-values binds multiple results in a definition (see Multiple Values and define-values), 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 ids. The binding rules are the same for the forms without -values forms: the ids of let-values are bound only in the bodys, the ids of let*-values s are bound in exprs of later clauses, and the ids of letrec-values are bound for all exprs.

Example:
> (let-values ([(qr)(quotient/remainder 143)])
(list qr))

'(4 2)

top
up

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /