Node: Binding Constructs, Next: , Previous: Conditionals, Up: Expressions



Binding Constructs

The three binding constructs let, let*, and letrec are available in STKLOS. These constructs 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.

let <bindings> <body> R5RS
let <variable> <bindings> <body> R5RS
In a let, <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.

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
          

The second form of let, which is generally called a named let, is a variant on the syntax of let which provides a more general looping construct than do (see do) and may also be used to express recursions. It has the same syntax and semantics as ordinary let except that <variable> is bound within <body> to a procedure whose formal arguments are the bound variables and whose body is <body>. Thus the execution of <body> may be repeated by invoking the procedure named by <variable>.

          (let loop ((numbers '(3 -2 1 6 -5))
                     (nonneg  '())
                     (neg     '()))
            (cond ((null? numbers) (list nonneg neg))
                  ((>= (car numbers) 0)
                     (loop (cdr numbers)
                           (cons (car numbers) nonneg)
                           neg))
                  ((< (car numbers) 0)
                     (loop (cdr numbers)
                            nonneg
                            (cons (car numbers) neg)))))
             =>  ((6 1 3) (-5 -2))
          

let* <bindings> <body> R5RS
In a let*, <bindings> should have the same form as in a let (however, a <variable> can appear more than once in the list of variables being bound).

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
          

letrec <bindings> <body> R5RS
<bindings> should have the form as in let.

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
          

fluid-let <bindings> <body> STKLOS Syntax
The <bindings> are evaluated in the current environment, in some unspecified order, the current values of the variables present in <bindings> are saved, and the new evaluated values are assigned to the <bindings> variables. Once this is done, the expressions of <body> are evaluated sequentially in the current environment; the value of the last expression is the result of fluid-let. Upon exit, the stored variables values are restored. An error is signalled if any of the <bindings> variable is unbound.
          (let* ((a 'out)
                 (f (lambda () a)))
            (list (f)
                  (fluid-let ((a 'in)) (f))
                  (f))) => (out in out)
          

When the body of a fluid-let is exited by invoking a continuation, the new variable values are saved, and the variables are set to their old values. Then, if the body is reentered by invoking a continuation, the old values are saved and new values are restored. The following example illustrates this behavior

          (let ((cont #f)
                (l    '())
                (a    'out))
            (set! l (cons a l))
            (fluid-let ((a 'in))
              (set! cont (call-with-current-continuation (lambda (k) k)))
              (set! l (cons a l)))
            (set! l (cons a l))
          
            (if cont (cont #f) l)) =>  (out in out in out)