Next: , Previous: , Up: Special Forms   [Contents][Index]


2.2 Lexical Binding

The binding constructs let, let*, letrec, letrec*, let-values, and let*-values give Scheme block structure, like Algol 60. The syntax of the first 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. The let-values and let*-values constructs are analogous to let and let* respectively, but are designed to handle multiple-valued expressions, binding different identifiers to the returned values.

extended standard special form: let ((variable init) …) expr expr …

The inits are evaluated in the current environment (in some unspecified order), the variables are bound to fresh locations holding the results, the exprs are evaluated sequentially in the extended environment, and the value of the last expr is returned. Each binding of a variable has the exprs as its region.

MIT/GNU Scheme allows any of the inits to be omitted, in which case the corresponding variables are unassigned.

Note that the following are equivalent:

(let ((variable init) …) expr expr …)
((lambda (variable …) expr expr …) init …)

Some examples:

(let ((x 2) (y 3))
  (* x y))                              ⇒  6

(let ((x 2) (y 3))
  (let ((foo (lambda (z) (+ x y z)))
        (x 7))
    (foo 4)))                           ⇒  9

See Iteration, for information on “named let”.

extended standard special form: let* ((variable init) …) expr expr …

let* is similar to let, but the bindings are performed sequentially from left to right, and the region of a binding 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.

Note that the following are equivalent:

(let* ((variable1 init1)
       (variable2 init2)
       …
       (variableN initN))
   expr
   expr …)

(let ((variable1 init1))
  (let ((variable2 init2))
    …
      (let ((variableN initN))
        expr
        expr …)
    …))

An example:

(let ((x 2) (y 3))
  (let* ((x 7)
         (z (+ x y)))
    (* z x)))                           ⇒  70
extended standard special form: letrec ((variable init) …) expr expr …

The variables are bound to fresh locations holding unassigned values, the inits are evaluated in the extended environment (in some unspecified order), each variable is assigned to the result of the corresponding init, the exprs are evaluated sequentially in the extended environment, and the value of the last expr is returned. Each binding of a variable has the entire letrec expression as its region, making it possible to define mutually recursive procedures.

MIT/GNU Scheme allows any of the inits to be omitted, in which case the corresponding variables are unassigned.

(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 shall be possible to evaluated 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 inits are lambda or delay expressions and the restriction is satisfied automatically.

extended standard special form: letrec* ((variable init) …) expr expr …

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

If it is not possible to evaluate each init without assigning or referring to the value of the corresponding variable or the variable of any of the bindings that follow it in bindings, it is an error. Another restriction is that it is an error to invoke the continuation of an init more than once.

;; Returns the arithmetic, geometric, and
;; harmonic means of a nested list of numbers
(define (means ton)
  (letrec*
     ((mean
        (lambda (f g)
          (f (/ (sum g ton) n))))
      (sum
        (lambda (g ton)
          (if (null? ton)
            (+)
            (if (number? ton)
                (g ton)
                (+ (sum g (car ton))
                   (sum g (cdr ton)))))))
      (n (sum (lambda (x) 1) ton)))
    (values (mean values values)
            (mean exp log)
            (mean / /))))

Evaluating (means '(3 (1 4))) returns three values: 8/3, 2.28942848510666 (approximately), and 36/19.

standard special form: let-values ((formals init) …) expr expr …

The inits are evaluated in the current environment (in some unspecified order) as if by invoking call-with-values, and the variables occurring in the formals are bound to fresh locations holding the values returned by the inits, where the formals are matched to the return values in the same way that the formals in a lambda expression are matched to the arguments in a procedure call. Then, the exprs are evaluated in the extended environment, and the values of the last expr are returned. Each binding of a variable has the exprs as its region.

It is an error if the formals do not match the number of values returned by the corresponding init.

(let-values (((root rem) (exact-integer-sqrt 32)))
  (* root rem))         ⇒  35
standard special form: let*-values ((formals init) …) expr expr …

The let*-values construct is similar to let-values, but the inits are evaluated and bindings created sequentially from left to right, with the region of the bindings of each formals including the inits to its right as well as body. Thus the second init is evaluated in an environment in which the first set of bindings is visible and initialized, and so on.

(let ((a 'a) (b 'b) (x 'x) (y 'y))
  (let*-values (((a b) (values x y))
                ((x y) (values a b)))
    (list a b x y)))    ⇒  (x y x y)

Next: Dynamic Binding, Previous: Lambda Expressions, Up: Special Forms   [Contents][Index]