Local binding constructs

The binding constructs let, let*, letrec, and letrec* give Scheme a block structure, like Algol 60. The syntax of these four 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 letrec and letrec* expressions, all the bindings are in effect while their initial values are being computed, thus allowing mutually recursive definitions.

Syntax: let ((pattern init) ...) body

Declare new local variables as found in the patterns. Each pattern is matched against the corresponding init. The inits are evaluated in the current environment (in left-to-right onder), the variables in the patternss are bound to fresh locations holding the matched results, the body is evaluated in the extended environment, and the values of the last expression of body 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

An example with a non-trivial pattern:

(let (([a::double b::integer] (vector 4 5)))
  (cons b a))  ⇒ (5 . 4.0)

Syntax: let* ((pattern init) ...) body

The let* binding construct is similar to let, but the bindings are performed sequentially from left to right, and the region of a variables in a pattern is that part of the let* expression to the right of the pattern. Thus the second pattern is matched in an environment in which the bindings from the first pattern are visible, and so on.

(let ((x 2) (y 3))
  (let* ((x 7)
         (z (+ x y)))
    (* z x)))  ⇒ 70

Syntax: letrec ((variable [:: type] init) ...) body

Syntax: letrec* ((variable [:: type] init) ...) body

The variables are bound to fresh locations, each variable is assigned in left-to-right order to the result of the corresponding init, the body is evaluated in the resulting environment, and the values of the last expression in body are returned. Despite the left-to-right evaluation and assignment order, each binding of a variable has the entire letrec or letrec* expression as its region, making it possible to define mutually recursive procedures.

In Kawa letrec is defined as the same as letrec*. In standard Scheme the order of evaluation of the inits is undefined, as is the order of assignments. If the order matters, you should use letrec*.

If it is not possible to evaluate each init without assigning or referring to the value of the corresponding variable or the variables that follow it, it is an error.

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