Chapter 4. Binding Forms

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

Section 4.1. Definitions

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

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

The Revised5 Report on Scheme [20] 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><variable definition>
|<syntax definition>
|(begin <definition>*)
|<derived definition>
<variable definition>(define <variable> <expression>)
|(define (<variable> <variable>*) <body>)
|(define (<variable> <variable>* . <variable>) <body>)
<syntax definition>(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, import, and import-only forms. Chez Scheme also permits the expression to be omitted from a variable definition, in which case the initial value is unspecified.

<definition><variable definition>
|<syntax definition>
|(begin <definition>*)
|(let-syntax (<syntax binding>*) <definition>*)
|(letrec-syntax (<syntax binding>*) <definition>*)
|<module form>
|<import form>
|<derived definition>
<variable definition>(define <variable> <expression>)
|(define <variable>)
|(define (<variable> <variable>*) <body>)
|(define (<variable> <variable>* . <variable>) <body>)
<syntax definition>(define-syntax <keyword> <transformer expression>)
<syntax binding>(<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 6.9.

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))  (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>(module <module name> <interface> <definition>* <init>*)
<module form>(module <interface> <definition>* <init>*)
<import form>(import <module name>)
|(import-only <module name>)
<export><identifier> | (<identifier> <export>*)

Modules are described in Section 9.3.

Section 4.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" [14].

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 4.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))  (0 1 3 6 10 15)

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

(eq? ((car cycle)) cycle)  #t

The definition below expands rec in terms of letrec.

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

Section 4.4. 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 [22], 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))  8

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

(let ([x 'a])
    (lambda (k)
       (fluid-let ([x 'b])
         (letrec ([f (lambda (y) (k '*))])
           (f '*)))))
  x)  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 4.5. 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)
returns: unspecified

define-top-level-value is used to establish a binding at top-level for the variable named by symbol to the value obj. define-top-level-value is similar to top-level define, 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 computed at run time.

  (define-top-level-value 'xyz "hi")
  xyz)  "hi"

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

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

set-top-level-value! assigns the variable named by symbol to the value obj. 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 computed at run time.

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

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

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

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

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

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)  #f

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

Chez Scheme User's Guide
© 1998 R. Kent Dybvig
Cadence Research Systems
Illustrations © 1998 Jean-Pierre Hébert
about this book