Chapter 5. Binding Forms

This chapter describes Chez Scheme extensions to the set of standard binding forms. See Chapter 4 of The Scheme Programming Language, Third Edition or the Revised5 Report on Scheme for a description of standard binding forms.

Section 5.1. Definitions

A definition in ANSI/IEEE Scheme is a variable definition or a sequence of definitions:

<definition><graphic><variable definition>
|(begin <definition>*)
<variable definition><graphic>(define <variable> <expression>)
|(define (<variable> <variable>*) <body>)
|(define (<variable> <variable>* . <variable>) <body>)

The Revised5 Report on Scheme [22] extends definitions to include syntactic (macro) definitions, though only at top level. The Revised5 Report also permits derived definitions, i.e., syntactic abstractions that expand into definitions.

<definition><graphic><variable definition>
|<syntax definition>
|(begin <definition>*)
|<derived definition>
<variable definition><graphic>(define <variable> <expression>)
|(define (<variable> <variable>*) <body>)
|(define (<variable> <variable>* . <variable>) <body>)
<syntax definition><graphic>(define-syntax <keyword> <transformer expression>)

Chez Scheme permits internal as well as top-level syntactic definitions and extends definitions to include let-syntax and letrec-syntax forms that contain definitions as well as module forms, import forms, import-only forms, meta definitions, and alias forms. Chez Scheme also permits the expression to be omitted from a variable definition, in which case the initial value is unspecified.

<definition><graphic><variable definition>
|<syntax definition>
|(begin <definition>*)
|(let-syntax (<syntax binding>*) <definition>*)
|(letrec-syntax (<syntax binding>*) <definition>*)
|<module form>
|<import form>
|<meta definition>
|<alias form>
|<derived definition>
<variable definition><graphic>(define <variable> <expression>)
|(define <variable>)
|(define (<variable> <variable>*) <body>)
|(define (<variable> <variable>* . <variable>) <body>)
<syntax definition><graphic>(define-syntax <keyword> <transformer expression>)
<syntax binding><graphic>(<keyword> <transformer expression>)

A begin containing definitions is treated as if the definitions are spliced into the surrounding context. For example,

(let ()
  (define x 1)
  (begin (define y 2) (define z 3))
  (+ x y z))

is equivalent to

(let ()
  (define x 1)
  (define y 2)
  (define z 3)
  (+ x y z))

and evaluates to 6. This form of begin is typically used to introduce multiple definitions into the expansion of a syntactic abstraction; see for example the definition of define-structure in Section 15.4.

A let-syntax or letrec-syntax form whose body contains definitions is treated like a begin form except that the keyword bindings established by the form are visible within the definitions.

(let ((a 0))
  (let-syntax ((a (identifier-syntax 4)))
    (define x a))
  (define y a) 
  (list x y)) <graphic> (4 0)

One consequence of this generalization is that let-syntax and letrec-syntax do not introduce local scopes as they are specified to do in the Revised5 Report. This effect can easily be achieved by inserting a let around the let-syntax or letrec-syntax body.

Module and import forms have the following syntax.

<module form><graphic>(module <module name> <interface> <definition>* <init>*)
<module form><graphic>(module <interface> <definition>* <init>*)
<import form><graphic>(import <import-spec>*)
|(import-only <import-spec>*)
<export><graphic><identifier> | (<identifier> <export>*)
|(only <import-spec> <id>*)
|(except <import-spec> <id>*)
|(add-prefix <import-spec> <id>)
|(drop-prefix <import-spec> <id>)
|(rename <import-spec> (<new-id> <old-id>)*)
|(alias <import-spec> (<new-id> <old-id>)*)

Module and import forms are described in Section 10.5.

Meta definitions have the following syntax.

<meta definition><graphic>(meta . <definition>)

Meta definitions are used to create expand-time variable bindings for use by macro transformers as described in Section 10.7.

Alias forms have the following syntax.

<alias form><graphic>(alias <identifier> <identifier>)

The form (alias x y) causes x to be treated as an alias for y, as described in Section 10.9.

The ANSI/IEEE Standard and the Revised5 Report specify that internal definitions be treated like letrec. This means that the order in which internal definitions are evaluated is unspecified. Furthermore, the semantics for letrec requires that all right-hand-side expressions be evaluated before any of the left-hand-side variables are assigned values. This means that it is an error to evaluate a reference to any left-hand-side variable before control has moved past the last definition in a sequence of internal definitions.

Chez Scheme Version 7 deviates from these standards in that it treats internal variable definitions like letrec* (page 5.3), which evaluates bindings from left-to-right and permits each right-hand-side expression to evaluate references to earlier left-hand-side variables. Left-to-right evaluation order of definitions is more convenient and makes the semantics of internal variable definitions more consistent with top-level variable definitions.

This deviation is unlikely to break any programs in practice, since the deviation cannot be detected with a standards-conforming program without obtaining a continuation while one of the definitions is evaluated and applying it after control has reached an assignment to one of the defined variables and verifying that the value has been reset to the original value.

The parameter internal-defines-as-letrec* may be set to #f to revert to the letrec semantics in the unlikely event that problems do arise.

parameter: internal-defines-as-letrec*

When set to #t (the default) internal variable definitions are evaluated using letrec* semantics. Otherwise, internal definitions are evaluated using letrec semantics.

Section 5.2. Case-Lambda

A Scheme lambda expression always produces a procedure with a fixed number of arguments or with an indefinite number of arguments greater than or equal to a certain number. In particular,

(lambda (var1 ... varnexp1 exp2 ...)

accepts exactly n arguments,

(lambda r exp1 exp2 ...)

accepts zero or more arguments, and

(lambda (var1 ... varn . rexp1 exp2 ...)

accepts n or more arguments.

lambda cannot directly produce, however, a procedure that accepts, say, either two or three arguments. In other words, procedures that accept optional arguments are not supported directly by lambda. The latter form of lambda shown above can be used, in conjunction with length checks and compositions of car and cdr, to implement procedures with optional arguments, though at the cost of clarity and efficiency.

Chez Scheme's case-lambda syntactic form directly supports procedures with optional arguments as well as procedures with fixed or indefinite numbers of arguments. case-lambda is based on the lambda* syntactic form introduced in the article "A New Approach to Procedures with Variable Arity" [16].

syntax: (case-lambda clause ...)
returns: a procedure

A case-lambda expression consists of a set of clauses, each resembling a lambda expression. Each clause has the form:

[formals exp1 exp2 ...]

The formal parameters for a clause are defined by formals in the same manner as for a lambda expression. The number of arguments accepted by a case-lambda expression is determined by the numbers of arguments accepted by its individual clauses.

When a procedure created with case-lambda is invoked, the clauses are considered in order. The first clause that accepts the given number of actual parameters is selected, the formal parameters defined by its formals are bound to the corresponding actual parameters, and the expressions exp1 exp2 ... are evaluated. If formals in a clause is a proper list of identifiers, then the clause accepts exactly as many actual parameters as there are formal parameters (identifiers) in formals. As with a lambda formals, a case-lambda clause formals may be a single identifier, in which case the clause accepts any number of arguments, or an improper list of identifiers terminated by an identifier, in which case the clause accepts any number of arguments greater than or equal to the number of formal parameters excluding the terminating identifier.

The following definition for make-list uses case-lambda to support the optional fill parameter.

(define make-list
  (rec make-list
       ; supply default element
       (make-list n '())]
      [(n x)
       (do ([n n (1- n)] [ls '() (cons x ls)])
           ((zero? n) ls))])))

The substring procedure may be extended with case-lambda to accept either no end index, in which case it defaults to the end of the string, or no start and end indices, in which case substring is equivalent to string-copy:

(define substring1
    [(s) (substring1 s 0 (string-length s))]
    [(s start) (substring1 s start (string-length s))]
    [(s start end) (substring s start end)]))

It is also possible to default the start index rather than the end index when only one index is supplied:

(define substring2
    [(s) (substring2 s 0 (string-length s))]
    [(s end) (substring2 s 0 end)]
    [(s start end) (substring s start end)]))

It is even possible to require that both or neither of the start and end indices be supplied, simply by leaving out the middle clause:

(define substring3
    [(s) (substring3 s 0 (string-length s))]
    [(s start end) (substring s start end)]))

Section 5.3. Recursive Bindings

syntax: (rec var exp)
returns: value of exp

The syntactic form rec creates a recursive object from exp by establishing a binding of var within exp to the value of exp. In essence, it is a special case of letrec for self-recursive objects.

This form is useful for creating recursive objects (especially procedures) that do not depend on external variables for the recursion, which are sometimes undesirable because the external bindings can change. For example, a recursive procedure defined at top level depends on the value of the top-level variable given as its name. If the value of this variable should change, the meaning of the procedure itself would change. If the procedure is defined instead with rec, its meaning is independent of the variable to which it is bound.

(map (rec sum
       (lambda (x)
         (if (= x 0)
             (+ x (sum (- x 1))))))
     '(0 1 2 3 4 5)) <graphic> (0 1 3 6 10 15)

(define cycle
  (rec self
    (list (lambda () self))))

(eq? ((car cycle)) cycle) <graphic> #t

The definition below expands rec in terms of letrec.

(define-syntax rec
  (syntax-rules ()
    ((_ x e) (letrec ((x e)) x))))

syntax: (letrec* ((var val) ...) exp1 exp2 ...)
returns: the value of the final expression

letrec* is similar to letrec. As with letrec, all of the expressions val ... are within the scope of all of the variables var .... Unlike letrec, however, letrec* evaluates the values in sequence from left to right. While it is an error for a reference to any var to be evaluated before the corresponding val has been evaluated, references to var may be evaluated any any time thereafter, including during the evaluation of the val of any subsequent binding.

A letrec* expression of the form

(letrec* ((var val) ...) body)

may be expressed in terms of let and set! as

(let ((var #f) ...)
  (set! var val) ...
  (let () body))

The outer let expression creates the bindings, the assignments evaluate the values and set the variables to these values, in sequence, and the inner let evaluates the body. let is used in the latter case rather than begin since the body may include internal definitions as well as expressions.

(letrec* ([sum (lambda (x)
                 (if (zero? x)
                     (+ x (sum (- x 1)))))]
          [f (lambda () (cons n n-sum))]
          [n 15]
          [n-sum (sum n)])
  (f)) <graphic> (15 120)

(letrec* ([f (lambda () (lambda () g))]
          [g (f)])
  (eq? (g) g)) <graphic> #t

(letrec* ([g (f)]
          [f (lambda () (lambda () g))])
  (eq? (g) g)) <graphic> error: attempt to reference undefined variable f.

Section 5.4. Multiple Values

syntax: (let-values ((formals val) ...) exp1 exp2 ...)
syntax: (let*-values ((formals val) ...) exp1 exp2 ...)
returns: value of the last expression

let-values is a convenient way to receive multiple values and bind them to variables. It is structured like let but permits an arbitrary formals list (like lambda) on each left-hand side. let*-values is similar but performs the bindings in left-to-right order, as with let*.

(let-values ([(a b) (values 1 2)] [c (values 1 2 3)])
  (list a b c)) <graphic> (1 2 (1 2 3))
(let*-values ([(a b) (values 1 2)] [(a b) (values b a)])
  (list a b)) <graphic> (2 1)

Section 5.5. Fluid Bindings

syntax: (fluid-let ((var val) ...) exp1 exp2 ...)
returns: value of the last expression

The syntactic form fluid-let provides a way to temporarily assign values to a set of variables. The new values are in effect only during the evaluation of the expression in the body of the fluid-let expression. The scopes of the variables are not determined by fluid-let; as with set!, the variables must be bound at top level or by an enclosing lambda or other binding form. It is possible, therefore, to control the scope of a variable with lambda or let while establishing a temporary value with fluid-let.

Although it is similar in appearance to let, its operation is more like that of set!. Each var is assigned, as with set!, to the value of the corresponding val within the body exp1 exp2 .... Should the body exit normally or by invoking a continuation made outside of the body (see call/cc), the values in effect before the bindings were changed are restored. Should control return back to the body by the invocation of a continuation created within the body, the bindings are changed once again to the values in effect when the body last exited.

Fluid bindings are most useful for maintaining variables that must be shared by a group of procedures. Upon entry to the group of procedures, the shared variables are fluidly bound to a new set of initial values so that on exit the original values are restored automatically. In this way, the group of procedures itself can be reentrant; it may call itself directly or indirectly without affecting the values of its shared variables.

Fluid bindings are similar to special bindings in Common Lisp [24], except that (1) there is a single namespace for both lexical and fluid bindings, and (2) the scope of a fluidly bound variable is not necessarily global.

(let ([x 3])
  (+ (fluid-let ([x 5])
     x)) <graphic> 8

(let ([x 'a])
  (letrec ([f (lambda (y) (cons x y))])
    (fluid-let ([x 'b])
      (f 'c)))) <graphic> (b . c)

(let ([x 'a])
    (lambda (k)
       (fluid-let ([x 'b])
         (letrec ([f (lambda (y) (k '*))])
           (f '*)))))
  x) <graphic> a

fluid-let may be defined in terms of dynamic-wind as follows.

(define-syntax fluid-let
  (lambda (x)
    (syntax-case x ()
      ((_ () e1 e2 ...) #'(let () e1 e2 ...))
      ((_ ((x v) ...) e1 e2 ...)
       (andmap identifier? #'(x ...))
       (with-syntax (((y ...) (generate-temporaries #'(x ...))))
         #'(let ((y v) ...)
             (let ((swap (lambda ()
                           (let ((t x)) (set! x y) (set! y t)) ...)))
               (dynamic-wind swap (lambda () e1 e2 ...) swap))))))))

Section 5.6. Top-Level Bindings

The procedures described in this section allow the direct manipulation of top-level values for Scheme variables. They are intended primarily to support the definition of interpreters or compilers for Scheme in Scheme but may be used to access or alter top-level values anywhere within a program whether at top level or not.

procedure: (define-top-level-value symbol obj)
procedure: (define-top-level-value symbol obj env)
returns: unspecified

define-top-level-value is used to establish a binding for the variable named by symbol to the value obj in the environment env. If no environment is provided, it defaults to the value of interaction-environment, i.e., the top-level evaluation environment (Section 11.2).

A call to define-top-level-value is similar to a top-level define form, except that a call to define-top-level-value need not occur at top-level and the variable for which the binding is to be established can be determined at run time, as can the environment.

  (define-top-level-value 'xyz "hi")
  xyz) <graphic> "hi"

(let ([var 'xyz])
  (define-top-level-value var "mom")
  (list var xyz)) <graphic> (xyz "mom")

procedure: (set-top-level-value! symbol obj)
procedure: (set-top-level-value! symbol obj env)
returns: unspecified

set-top-level-value! assigns the variable named by symbol to the value obj in the environment env. If env is not provided, it defaults to the value of interaction-environment, i.e., the top-level evaluation environment (Section 11.2).

set-top-level-value! is similar to set! when set! is used on top-level variables except that the variable to be assigned can be determined at run time, as can the environment.

(let ((v (let ((cons list))
           (set-top-level-value! 'cons +)
           (cons 3 4))))
  (list v (cons 3 4))) <graphic> ((3 4) 7)

procedure: (top-level-value symbol)
procedure: (top-level-value symbol env)
returns: the top-level value of the variable named by symbol in env

If no environment is provided, it defaults to the value of interaction-environment, i.e., the top-level evaluation environment (Section 11.2).

An error is signaled if the variable named by symbol is not defined.

top-level-value is similar to a top-level variable reference except that the variable to be referenced can be determined at run time, as can the environment.

(let ((cons +))
  (list (cons 3 4)
        ((top-level-value 'cons) 3 4))) <graphic> (7 (3 . 4))

(define e (copy-environment (scheme-environment)))
(define-top-level-value 'pi 3.14 e)
(top-level-value 'pi e) <graphic> 3.14
(set-top-level-value! 'pi 3.1416 e)
(top-level-value 'pi e) <graphic> 3.1416

procedure: (top-level-bound? symbol)
procedure: (top-level-bound? symbol env)
returns: #t if symbol is defined in env, #f otherwise

If no environment is provided, it defaults to the value of interaction-environment, i.e., the top-level evaluation environment (Section 11.2).

This predicate is useful in an interpreter to check for the existence of a top-level binding before requesting the value with top-level-value.

(top-level-bound? 'xyz) <graphic> #f

  (define-top-level-value 'xyz 3)
  (top-level-bound? 'xyz)) <graphic> #t

(define e (copy-environment (interaction-environment)))
(define-top-level-value 'pi 3.14 e)
(top-level-bound? 'pi) <graphic> #f
(top-level-bound? 'pi e) <graphic> #t

R. Kent Dybvig / Chez Scheme Version 7 User's Guide
Copyright © 2005 R. Kent Dybvig
Revised July 2007 for Chez Scheme Version 7.4
Cadence Research Systems /
Cover illustration © 1998 Jean-Pierre Hébert
ISBN: 0-9667139-1-5
to order this book / about this book