[previous] [up] [next]


4.2.2 Binding constructs

The three binding constructs let, let*, and letrec give Scheme a block structure, like Algol 60. The syntax of the three constructs is identical, but they differ in the regions they establish for their variable bindings. In a let expression, the initial values are computed before any of the variables become bound; in a let* expression, the bindings and evaluations are performed sequentially; while in a letrec expression, all the bindings are in effect while their initial values are being computed, thus allowing mutually recursive definitions.

Syntax:  (let <bindings> <body>)

Syntax: <Bindings> should have the form

    ((<variable1> <init1>) ...)

where each <init> is an expression, and <body> should be a sequence of one or more expressions. It is an error for a <variable> to appear more than once in the list of variables being bound.

Semantics: The <init>s are evaluated in the current environment (in some unspecified order), the <variable>s are bound to fresh locations holding the results, the <body> is evaluated in the extended environment, and the value(s) of the last expression of <body> is(are) returned. Each binding of a <variable> has <body> as its region.

  (let ((x 2) (y 3))
    (* x y))                      =>  6

  (let ((x 2) (y 3))
    (let ((x 7)
          (z (+ x y)))
      (* z x)))                   =>  35

See also named let, section 4.2.4.

Syntax:  (let* <bindings> <body>)

Syntax: <Bindings> should have the form

    ((<variable1> <init1>) ...),

and <body> should be a sequence of one or more expressions.

Semantics: Let* is similar to let, but the bindings are performed sequentially from left to right, and the region of a binding indicated by (<variable> <init>) is that part of the let* expression to the right of the binding. Thus the second binding is done in an environment in which the first binding is visible, and so on.

  (let ((x 2) (y 3))
    (let* ((x 7)
           (z (+ x y)))
      (* z x)))             =>  70
Syntax:  (letrec <bindings> <body>)

Syntax: <Bindings> should have the form

    ((<variable1> <init1>) ...),

and <body> should be a sequence of one or more expressions. It is an error for a <variable> to appear more than once in the list of variables being bound.

Semantics: The <variable>s are bound to fresh locations holding undefined values, the <init>s are evaluated in the resulting environment (in some unspecified order), each <variable> is assigned to the result of the corresponding <init>, the <body> is evaluated in the resulting environment, and the value(s) of the last expression in <body> is(are) returned. Each binding of a <variable> has the entire letrec expression as its region, making it possible to define mutually recursive procedures.

  (letrec ((even?
            (lambda (n)
              (if (zero? n)
                  #t
                  (odd? (- n 1)))))
           (odd?
            (lambda (n)
              (if (zero? n)
                  #f
                  (even? (- n 1))))))
    (even? 88))
                  =>  #t

One restriction on letrec is very important: it must be possible to evaluate each <init> without assigning or referring to the value of any <variable>. If this restriction is violated, then it is an error. The restriction is necessary because Scheme passes arguments by value rather than by name. In the most common uses of letrec, all the <init>s are lambda expressions and the restriction is satisfied automatically.