[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
A special form is an expression that follows special evaluation rules. This chapter describes the basic Scheme special forms.
2.1 Lambda Expressions 2.2 Lexical Binding 2.3 Dynamic Binding 2.4 Definitions 2.5 Assignments 2.6 Quoting 2.7 Conditionals 2.8 Sequencing 2.9 Iteration 2.10 Structure Definitions 2.11 Macros
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
lambda
expression evaluates to a procedure. The environment in
effect when the lambda
expression is evaluated is remembered as
part of the procedure; it is called the closing environment. When
the procedure is later called with some arguments, the closing
environment is extended by binding the variables in the formal parameter
list to fresh locations, and the locations are filled with the arguments
according to rules about to be given. The new environment created by
this process is referred to as the invocation environment.
Once the invocation environment has been constructed, the
expressions in the body of the lambda
expression are
evaluated sequentially in it. This means that the region of the
variables bound by the lambda
expression is all of the
expressions in the body. The result of evaluating the last
expression in the body is returned as the result of the procedure
call.
Formals, the formal parameter list, is often referred to as a lambda list.
The process of matching up formal parameters with arguments is somewhat involved. There are three types of parameters, and the matching treats each in sequence:
condition-type:wrong-number-of-arguments
is signalled;
this error is also signalled if there are more arguments than required
parameters and there are no further parameters.
condition-type:wrong-number-of-arguments
is signalled.
The predicate default-object?
, which is true only of default
objects, can be used to determine which optional parameters were
supplied, and which were defaulted.
In Scheme, unlike some other Lisp implementations, the list to which a rest parameter is bound is always freshly allocated. It has infinite extent and may be modified without affecting the procedure's caller.
Specially recognized keywords divide the formals parameters into these three classes. The keywords used here are `#!optional', `.', and `#!rest'. Note that only `.' is defined by standard Scheme -- the other keywords are MIT Scheme extensions. `#!rest' has the same meaning as `.' in formals.
The use of these keywords is best explained by means of examples. The following are typical lambda lists, followed by descriptions of which parameters are required, optional, and rest. We will use `#!rest' in these examples, but anywhere it appears `.' could be used instead.
(a b c)
a
, b
, and c
are all required. The procedure must
be passed exactly three arguments.
(a b #!optional c)
a
and b
are required, c
is optional. The procedure
may be passed either two or three arguments.
(#!optional a b c)
a
, b
, and c
are all optional. The procedure may be
passed any number of arguments between zero and three, inclusive.
a
(#!rest a)
a
is a rest parameter. The
procedure may be passed any number of arguments. Note: this is the only
case in which `.' cannot be used in place of `#!rest'.
(a b #!optional c d #!rest e)
a
and b
are required, c
and d
are optional,
and e
is rest. The procedure may be passed two or more
arguments.
Some examples of lambda
expressions:
(lambda (x) (+ x x)) => #[compound-procedure 53] ((lambda (x) (+ x x)) 4) => 8 (define reverse-subtract (lambda (x y) (- y x))) (reverse-subtract 7 10) => 3 (define foo (let ((x 4)) (lambda (y) (+ x y)))) (foo 6) => 10 |
named-lambda
special form is similar to lambda
, except
that the first "required parameter" in formals is not a
parameter but the name of the resulting procedure; thus
formals must have at least one required parameter. This name has
no semantic meaning, but is included in the external representation of
the procedure, making it useful for debugging. In MIT Scheme,
lambda
is implemented as named-lambda
, with a special name
that means "unnamed".
(named-lambda (f x) (+ x x)) => #[compound-procedure 53 f] ((named-lambda (f x) (+ x x)) 4) => 8 |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The three binding constructs let
, let*
, and letrec
,
give Scheme block structure. The syntax of the three 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 evaluations and bindings are sequentially interleaved.
And in a letrec
expression, all the bindings are in effect while
the initial values are being computed (thus allowing mutually recursive
definitions).
MIT 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) ...) expression expression ...) ((lambda (variable ...) expression expression ...) 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 section 2.9 Iteration, for information on "named let
".
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)) expression expression ...) (let ((variable1 init1)) (let ((variable2 init2)) ... (let ((variableN initN)) expression expression ...) ...)) |
An example:
(let ((x 2) (y 3)) (let* ((x 7) (z (+ x y))) (* z x))) => 70 |
letrec
expression as its region, making it possible to
define mutually recursive procedures.
MIT 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.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The syntax of this special form is similar to that of let
, but
fluid-let
temporarily rebinds existing variables. Unlike
let
, fluid-let
creates no new bindings; instead it
assigns the value of each init to the binding (determined
by the rules of lexical scoping) of its corresponding variable.
MIT Scheme allows any of the inits to be omitted, in which case the corresponding variables are temporarily unassigned.
An error of type condition-type:unbound-variable
is signalled if
any of the variables are unbound. However, because
fluid-let
operates by means of side effects, it is valid for any
variable to be unassigned when the form is entered.
Here is an example showing the difference between fluid-let
and
let
. First see how let
affects the binding of a variable:
(define variable #t) (define (access-variable) variable) variable => #t (let ((variable #f)) (access-variable)) => #t variable => #t |
access-variable
returns #t
in this case because it
is defined in an environment with variable
bound to
#t
. fluid-let
, on the other hand, temporarily reuses an
existing variable:
variable => #t (fluid-let ((variable #f)) ;reuses old binding (access-variable)) => #f variable => #t |
The extent of a dynamic binding is defined to be the time period during which the variable contains the new value. Normally this time period begins when the body is entered and ends when it is exited; on a sequential machine it is normally a contiguous time period. However, because Scheme has first-class continuations, it is possible to leave the body and then reenter it, as many times as desired. In this situation, the extent becomes non-contiguous.
When the body is exited by invoking a continuation, the new value is saved, and the variable is set to the old value. Then, if the body is reentered by invoking a continuation, the old value is saved, and the variable is set to the new value. In addition, side effects to the variable that occur both inside and outside of body are preserved, even if continuations are used to jump in and out of body repeatedly.
Here is a complicated example that shows the interaction between dynamic binding and continuations:
(define (complicated-dynamic-binding) (let ((variable 1) (inside-continuation)) (write-line variable) (call-with-current-continuation (lambda (outside-continuation) (fluid-let ((variable 2)) (write-line variable) (set! variable 3) (call-with-current-continuation (lambda (k) (set! inside-continuation k) (outside-continuation #t))) (write-line variable) (set! inside-continuation #f)))) (write-line variable) (if inside-continuation (begin (set! variable 4) (inside-continuation #f))))) |
Evaluating `(complicated-dynamic-binding)' writes the following on the console:
1 2 1 3 4 |
Commentary: the first two values written are the initial binding of
variable
and its new binding after the fluid-let
's body is
entered. Immediately after they are written, variable
is set to
`3', and then outside-continuation
is invoked, causing us to
exit the body. At this point, `1' is written, demonstrating that
the original value of variable
has been restored, because we have
left the body. Then we set variable
to `4' and reenter the
body by invoking inside-continuation
. At this point, `3' is
written, indicating that the side effect that previously occurred within
the body has been preserved. Finally, we exit body normally, and write
`4', demonstrating that the side effect that occurred outside of
the body was also preserved.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
lambda
,
let
, let*
, letrec
, fluid-let
, or "procedure
define
" expression). A definition that occurs at the top level
of a program is called a top-level definition, and a definition
that occurs at the beginning of a body is called an internal
definition.
In the second form of define
(called "procedure
define
"), the component formals is identical to the
component of the same name in a named-lambda
expression. In
fact, these two expressions are equivalent:
(define (name1 name2 ...) expression expression ...) (define name1 (named-lambda (name1 name2 ...) expression expression ...)) |
2.4.1 Top-Level Definitions 2.4.2 Internal Definitions
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(define variable expression) |
has essentially the same effect as this assignment expression, if variable is bound:
(set! variable expression) |
If variable is not bound, however, define
binds
variable to a new location in the current environment before
performing the assignment (it is an error to perform a set!
on an
unbound variable). If you omit expression, the variable becomes
unassigned; an attempt to reference such a variable is an error.
(define add3 (lambda (x) (+ x 3))) => unspecified (add3 3) => 6 (define first car) => unspecified (first '(1 2)) => 1 (define bar) => unspecified bar error--> Unassigned variable |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
An internal definition is a definition that occurs at the
beginning of a body (that is, the body of a lambda
,
let
, let*
, letrec
, fluid-let
, or "procedure
define
" expression), rather than at the top level of a program.
The variable defined by an internal definition is local to the
body. That is, variable is bound rather than assigned, and
the region of the binding is the entire body. For example,
(let ((x 5)) (define foo (lambda (y) (bar x y))) (define bar (lambda (a b) (+ (* a b) a))) (foo (+ x 3))) => 45 |
A body containing internal definitions can always be converted
into a completely equivalent letrec
expression. For example, the
let
expression in the above example is equivalent to
(let ((x 5)) (letrec ((foo (lambda (y) (bar x y))) (bar (lambda (a b) (+ (* a b) a)))) (foo (+ x 3)))) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
set!
expression is unspecified.
Variable must be bound either in some region enclosing the
set!
expression, or at the top level. However, variable is
permitted to be unassigned when the set!
form is entered.
(define x 2) => unspecified (+ x 1) => 3 (set! x 4) => unspecified (+ x 1) => 5 |
Variable may be an access
expression
(see section 13. Environments). This allows you to assign variables in an
arbitrary environment. For example,
(define x (let ((y 0)) (the-environment))) (define y 'a) y => a (access y x) => 0 (set! (access y x) 1) => unspecified y => a (access y x) => 1 |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This section describes the expressions that are used to modify or prevent the evaluation of objects.
(quote datum)
evaluates to datum. Datum may be
any external representation of a Scheme object
(see section 1.2.6 External Representations).
Use quote
to include literal constants in
Scheme code.
(quote a) => a (quote #(a b c)) => #(a b c) (quote (+ 1 2)) => (+ 1 2) |
(quote datum)
may be abbreviated as 'datum
.
The two notations are equivalent in all respects.
'a => a '#(a b c) => #(a b c) '(+ 1 2) => (+ 1 2) '(quote a) => (quote a) ''a => (quote a) |
Numeric constants, string constants, character constants, and boolean constants evaluate to themselves, so they don't need to be quoted.
'"abc" => "abc" "abc" => "abc" '145932 => 145932 145932 => 145932 '#t => #t #t => #t '#\a => #\a #\a => #\a |
`template
is
equivalent (in the sense of equal?
) to the result of evaluating
'template
. If a comma appears within the template,
however, the expression following the comma is evaluated ("unquoted")
and its result is inserted into the structure instead of the comma and
the expression. If a comma appears followed immediately by an at-sign
(@), then the following expression shall evaluate to a list; the
opening and closing parentheses of the list are then "stripped away"
and the elements of the list are inserted in place of the comma at-sign
expression sequence.
`(list ,(+ 1 2) 4) => (list 3 4) (let ((name 'a)) `(list ,name ',name)) => (list a 'a) `(a ,(+ 1 2) ,@(map abs '(4 -5 6)) b) => (a 3 4 5 6 b) `((foo ,(- 10 3)) ,@(cdr '(c)) . ,(car '(cons))) => ((foo 7) . cons) `#(10 5 ,(sqrt 4) ,@(map sqrt '(16 9)) 8) => #(10 5 2 4 3 8) `,(+ 2 3) => 5 |
Quasiquote forms may be nested. Substitutions are made only for unquoted components appearing at the same nesting level as the outermost backquote. The nesting level increases by one inside each successive quasiquotation, and decreases by one inside each unquotation.
`(a `(b ,(+ 1 2) ,(foo ,(+ 1 3) d) e) f) => (a `(b ,(+ 1 2) ,(foo 4 d) e) f) (let ((name1 'x) (name2 'y)) `(a `(b ,,name1 ,',name2 d) e)) => (a `(b ,x ,'y d) e) |
The notations `template
and (quasiquote
template
) are identical in all respects.
,expression
is identical to (unquote
expression)
and ,@expression
is identical to
(unquote-splicing expression)
.
(quasiquote (list (unquote (+ 1 2)) 4)) => (list 3 4) '(quasiquote (list (unquote (+ 1 2)) 4)) => `(list ,(+ 1 2) 4) i.e., (quasiquote (list (unquote (+ 1 2)) 4)) |
Unpredictable behavior can result if any of the symbols
quasiquote
, unquote
, or unquote-splicing
appear in
a template in ways otherwise than as described above.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The behavior of the conditional expressions is determined by
whether objects are true or false. The conditional expressions count
only #f
as false. They count everything else, including
#t
, pairs, symbols, numbers, strings, vectors, and procedures as
true (but see section 1.2.5 True and False).
In the descriptions that follow, we say that an object has "a true value" or "is true" when the conditional expressions treat it as true, and we say that an object has "a false value" or "is false" when the conditional expressions treat it as false.
if
expression is evaluated as follows: first,
predicate is evaluated. If it yields a true value, then
consequent is evaluated and its value is returned. Otherwise
alternative is evaluated and its value is returned. If
predicate yields a false value and no alternative is
specified, then the result of the expression is unspecified.
An if
expression evaluates either consequent or
alternative, never both. Programs should not depend on the value
of an if
expression that has no alternative.
(if (> 3 2) 'yes 'no) => yes (if (> 2 3) 'yes 'no) => no (if (> 3 2) (- 3 2) (+ 3 2)) => 1 |
(predicate expression ...) |
where predicate is any expression. The last clause may be
an else
clause, which has the form:
(else expression expression ...) |
A cond
expression does the following:
cond
evaluates
the expressions in the associated clause in left to right
order, and returns the result of evaluating the last expression in
the clause as the result of the entire cond
expression.
If the selected clause contains only the predicate and no
expressions, cond
returns the value of the predicate
as the result.
else
clause, the result of the conditional expression is
unspecified; if there is an else
clause, cond
evaluates
its expressions (left to right) and returns the value of the last
one.
(cond ((> 3 2) 'greater) ((< 3 2) 'less)) => greater (cond ((> 3 3) 'greater) ((< 3 3) 'less) (else 'equal)) => equal |
Normally, programs should not depend on the value of a cond
expression that has no else
clause. However, some Scheme
programmers prefer to write cond
expressions in which at least
one of the predicates is always true. In this style, the final
clause is equivalent to an else
clause.
Scheme supports an alternative clause syntax:
(predicate => recipient) |
where recipient is an expression. If predicate evaluates to a true value, then recipient is evaluated. Its value must be a procedure of one argument; this procedure is then invoked on the value of the predicate.
(cond ((assv 'b '((a 1) (b 2))) => cadr) (else #f)) => 2 |
((object ...) expression expression ...) |
No object is evaluated, and all the objects must be
distinct. The last clause may be an else
clause,
which has the form:
(else expression expression ...) |
A case
expression does the following:
eqv?
; see section 3. Equivalence Predicates) to an object,
case
evaluates the expressions in the corresponding
clause from left to right and returns the result of evaluating the
last expression in the clause as the result of the
case
expression.
else
clause, case
evaluates its expressions and returns the result of the last one
as the result of the case
expression. If there's no else
clause, case
returns an unspecified result. Programs should not
depend on the value of a case
expression that has no else
clause.
For example,
(case (* 2 3) ((2 3 5 7) 'prime) ((1 4 6 8 9) 'composite)) => composite (case (car '(c d)) ((a) 'a) ((b) 'b)) => unspecified (case (car '(c d)) ((a e i o u) 'vowel) ((w y) 'semivowel) (else 'consonant)) => consonant |
#t
is returned.
(and (= 2 2) (> 2 1)) => #t (and (= 2 2) (< 2 1)) => #f (and 1 2 'c '(f g)) => (f g) (and) => #t |
#f
is returned.
(or (= 2 2) (> 2 1)) => #t (or (= 2 2) (< 2 1)) => #t (or #f #f #f) => #f (or (memq 'b '(a b c)) (/ 3 0)) => (b c) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The begin
special form is used to evaluate expressions in a
particular order.
(define x 0) (begin (set! x 5) (+ x 1)) => 6 (begin (display "4 plus 1 equals ") (display (+ 4 1))) -| 4 plus 1 equals 5 => unspecified |
Often the use of begin
is unnecessary, because many special forms
already support sequences of expressions (that is, they have an implicit
begin
). Some of these special forms are:
case
cond
define ;``procedure |
The obsolete special form sequence
is identical to begin
.
It should not be used in new code.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
The iteration expressions are: "named let
" and do
.
They are also binding expressions, but are more commonly referred to as
iteration expressions. Because Scheme is properly tail-recursive, you
don't need to use these special forms to express iteration; you can
simply use appropriately written "recursive" procedure calls.
let
called
"named let
" which provides a more general looping construct
than do
, and may also be used to express recursions.
Named let
has the same syntax and semantics as ordinary
let
except that name is bound within the expressions
to a procedure whose formal arguments are the variables and whose
body is the expressions. Thus the execution of the
expressions may be repeated by invoking the procedure named by
name.
MIT Scheme allows any of the inits to be omitted, in which case the corresponding variables are unassigned.
Note: the following expressions are equivalent:
(let name ((variable init) ...) expression expression ...) ((letrec ((name (named-lambda (name variable ...) expression expression ...))) name) init ...) |
Here is an example:
(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)) (else (loop (cdr numbers) nonneg (cons (car numbers) neg))))) => ((6 1 3) (-5 -2)) |
do
is an iteration construct. It specifies a set of variables to
be bound, how they are to be initialized at the start, and how they are
to be updated on each iteration. When a termination condition is met,
the loop exits with a specified result value.
do
expressions are evaluated as follows: The init
expressions are evaluated (in some unspecified order), the
variables are bound to fresh locations, the results of the
init expressions are stored in the bindings of the
variables, and then the iteration phase begins.
Each iteration begins by evaluating test; if the result is false, then the command expressions are evaluated in order for effect, the step expressions are evaluated in some unspecified order, the variables are bound to fresh locations, the results of the steps are stored in the bindings of the variables, and the next iteration begins.
If test evaluates to a true value, then the expressions are
evaluated from left to right and the value of the last expression
is returned as the value of the do
expression. If no
expressions are present, then the value of the do
expression is unspecified in standard Scheme; in MIT Scheme, the
value of test is returned.
The region of the binding of a variable consists of the entire
do
expression except for the inits. It is an error for a
variable to appear more than once in the list of do
variables.
A step may be omitted, in which case the effect is the same as if
(variable init variable)
had been written
instead of (variable init)
.
(do ((vec (make-vector 5)) (i 0 (+ i 1))) ((= i 5) vec) (vector-set! vec i i)) => #(0 1 2 3 4) (let ((x '(1 3 5 7 9))) (do ((x x (cdr x)) (sum 0 (+ sum (car x)))) ((null? x) sum))) => 25 |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This section provides examples and describes the options and syntax of
define-structure
, an MIT Scheme macro that is very similar to
defstruct
in Common Lisp. The differences between them are
summarized at the end of this section. For more information, see
Steele's Common Lisp book.
slot-name (slot-name default-init [slot-option value]*) |
The fields name and slot-name must both be symbols. The field default-init is an expression for the initial value of the slot. It is evaluated each time a new instance is constructed. If it is not specified, the initial content of the slot is undefined. Default values are only useful with a BOA constructor with argument list or a keyword constructor (see below).
Evaluation of a define-structure
expression defines a structure
descriptor and a set of procedures to manipulate instances of the
structure. These instances are represented as records by default
(see section 10.4 Records) but may alternately be lists or vectors. The
accessors and modifiers are marked with compiler declarations so that
calls to them are automatically transformed into appropriate references.
Often, no options are required, so a simple call to
define-structure
looks like:
(define-structure foo a b c) |
This defines a type descriptor foo
, a constructor
make-foo
, a predicate foo?
, accessors foo-a
,
foo-b
, and foo-c
, and modifiers set-foo-a!
,
set-foo-b!
, and set-foo-c!
.
In general, if no options are specified, define-structure
defines
the following (using the simple call above as an example):
record-type?
.
"make-"
followed by the name of
the structure, e.g. `make-foo'. The number of arguments accepted
by the constructor is the same as the number of slots; the arguments are
the initial values for the slots, and the order of the arguments matches
the order of the slot definitions.
"?"
, e.g. `foo?'. The predicate is a procedure of one
argument, which returns #t
if its argument is a record of the
type defined by this structure definition, and #f
otherwise.
"set-"
, the name of the accessor, and
"!"
, e.g. `set-foo-a!'. The modifier is a procedure of
two arguments, the first of which must be a record of the type defined
by this structure definition, and the second of which may be any object.
The modifier modifies the contents of the corresponding slot in that
record to be that object, and returns an unspecified value.
When options are not supplied, (name)
may be abbreviated to
name. This convention holds equally for structure-options
and slot-options. Hence, these are equivalent:
(define-structure foo a b c) (define-structure (foo) (a) b (c)) |
as are
(define-structure (foo keyword-constructor) a b c) (define-structure (foo (keyword-constructor)) a b c) |
When specified as option values, false
and nil
are
equivalent to #f
, and true
and t
are equivalent to
#t
.
Possible slot-options are:
#f
, this specifies that no
modifier should be created for the slot.
Possible structure-options are:
#f
, the predicate
is not defined at all. Otherwise, name must be a symbol, and
the predicate is defined with that symbol as its name.
"copy-"
followed by the structure name (e.g.
`copy-foo'). If name is #f
, the copier is not
defined. Otherwise, name must be a symbol, and the copier is
defined with that symbol as its name.
set-record-type-unparser-method!
.
If name is not given, a constructor is defined with the default
name and arguments (see above). If name is #f
, no
constructor is defined; argument-list may not be specified in this
case. Otherwise, name must be a symbol, and a constructor is
defined with that symbol as its name. If name is a symbol,
argument-list is optionally allowed; if it is omitted, the
constructor accepts one argument for each slot in the structure
definition, in the same order in which the slots appear in the
definition. Otherwise, argument-list must be a lambda list
(see section 2.1 Lambda Expressions), and each of the parameters of the lambda
list must be the name of a slot in the structure. The arguments
accepted by the constructor are defined by this lambda list. Any slot
that is not specified by the lambda list is initialized to the
default-init as specified above; likewise for any slot specified
as an optional parameter when the corresponding argument is not
supplied.
If the constructor
option is specified, the default constructor
is not defined. Additionally, the constructor
option may be
specified multiple times to define multiple constructors with
different names and argument lists.
(define-structure (foo (constructor make-foo (#!optional a b))) (a 6 read-only #t) (b 9)) |
"make-"
followed by the name of the structure (e.g.
`make-foo'). Otherwise, name must be a symbol, and a keyword
constructor is defined with this symbol as its name.
If the keyword-constructor
option is specified, the default
constructor is not defined. Additionally, the
keyword-constructor
option may be specified multiple times to
define multiple keyword constructors; this is usually not done since
such constructors would all be equivalent.
(define-structure (foo (keyword-constructor make-bar)) a b) (foo-a (make-bar 'b 20 'a 19)) => 19 |
type
or named
options.
By default, structures are implemented as records. The name of the
structure is defined to hold the type descriptor of the record defined
by the structure. The type-descriptor
option specifies a
different name to hold the type descriptor.
(define-structure foo a b) foo => #[record-type 18] (define-structure (bar (type-descriptor bar-rtd)) a b) bar error--> Unbound variable: bar bar-rtd => #[record-type 19] |
conc-name
option can be
used to specify an alternative. If name is not given, the prefix
is the name of the structure followed by a hyphen (the default). If
name is #f
, the slot names are used directly, without
prefix. Otherwise, name must a symbol, and that symbol is used as
the prefix.
|
defines accessors moby/a
and moby/b
, and modifiers
set-moby/a!
and set-moby/b!
.
|
defines accessors a
and b
, and modifiers set-a!
and
set-b!
.
type-descriptor
option.
By default, structures are implemented as records. The type
option overrides this default, allowing the programmer to specify that
the structure be implemented using another data type. The option value
representation-type specifies the alternate data type; it is
allowed to be one of the symbols vector
or list
, and the
data type used is the one corresponding to the symbol.
If this option is given, and the named
option is not specified,
the representation will not be tagged, and neither a predicate nor a
type descriptor will be defined; also, the print-procedure
option may not be given.
(define-structure (foo (type list)) a b) (make-foo 1 2) => (1 2) |
type
option and
specifies that the structure instances be tagged to make them
identifiable as instances of this structure type. This option cannot be
used with the type-descriptor
option.
In the usual case, where expression is not given, the named
option causes a type descriptor and predicate to be defined for the
structure (recall that the type
option without named
suppresses their definition), and also defines a default unparser method
for the structure instances (which can be overridden by the
print-procedure
option). If the default unparser method is not
wanted then the print-procedure
option should be specified as
#F
. This causes the structure to be printed in its native
representation, as a list or vector, which includes the type descriptor.
The type descriptor is a unique object, not a record type, that
describes the structure instances and is additionally stored in the
structure instances to identify them: if the representation type is
vector
, the type descriptor is stored in the zero-th slot of the
vector, and if the representation type is list
, it is stored as
the first element of the list.
(define-structure (foo (type vector) named) a b c) (vector-ref (make-foo 1 2 3) 0) => #[structure-type 52] |
If expression is specified, it is an expression that is evaluated to yield a tag object. The expression is evaluated once when the structure definition is evaluated (to specify the unparser method), and again whenever a predicate or constructor is called. Because of this, expression is normally a variable reference or a constant. The value yielded by expression may be any object at all. That object is stored in the structure instances in the same place that the type descriptor is normally stored, as described above. If expression is specified, no type descriptor is defined, only a predicate.
(define-structure (foo (type vector) (named 'foo)) a b c) (vector-ref (make-foo 1 2 3) 0) => foo |
define-structure
. If safe-accessors
is not specified, or
if boolean is #f
, then the accessors are optimized for
speed at the expense of safety; when compiled, the accessors will turn
into very fast inline sequences, usually one to three machine
instructions in length. However, if safe-accessors
is specified
and boolean is either omitted or #t
, then the accessors are
optimized for safety, will check the type and structure of their
argument, and will be close-coded.
(define-structure (foo safe-accessors) a b c) |
type
option.
Offset must be an exact non-negative integer and specifies the
number of slots to leave open at the beginning of the structure instance
before the specified slots are allocated. Specifying an offset of
zero is equivalent to omitting the initial-offset
option.
If the named
option is specified, the structure tag appears in
the first slot, followed by the "offset" slots, and then the regular
slots. Otherwise, the "offset" slots come first, followed by the
regular slots.
(define-structure (foo (type vector) (initial-offset 3)) a b c) (make-foo 1 2 3) => #(() () () 1 2 3) |
The essential differences between MIT Scheme's define-structure
and Common Lisp's defstruct
are:
keyword-constructor
.
&aux
in Scheme lambda lists, this
functionality is not implemented.
copier
procedure is defined.
foo
is
given the name set-foo!
.
foo
instead of :foo
.
false
, nil
, true
, and t
are treated as if the appropriate boolean constant had been specified
instead.
print-function
option is named print-procedure
. Its
argument is a procedure of two arguments (the unparser state and the
structure instance) rather than three as in Common Lisp.
named
option may optionally take an argument, which is
normally the name of a variable (any expression may be used, but it is
evaluated whenever the tag name is needed). If used, structure
instances will be tagged with that variable's value. The variable must
be defined when define-structure
is evaluated.
type
option is restricted to the values vector
and
list
.
include
option is not implemented.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
(This section is largely taken from the Revised^4 Report on the Algorithmic Language Scheme. The section on Syntactic Closures is derived from a document written by Chris Hanson. The section on Explicit Renaming is derived from a document written by William Clinger.)
Scheme programs can define and use new derived expression types, called macros. Program-defined expression types have the syntax
(keyword datum ...) |
where keyword is an identifier that uniquely determines the expression type. This identifier is called the syntactic keyword, or simply keyword, of the macro. The number of the datums, and their syntax, depends on the expression type.
Each instance of a macro is called a use of the macro. The set of rules that specifies how a use of a macro is transcribed into a more primitive expression is called the transformer of the macro.
MIT Scheme also supports anonymous syntactic keywords. This means that it's not necessary to binding a macro transformer to a syntactic keyword before it is used. Instead, any macro-transformer expression can appear as the first element of a form, and the form will be expanded by the transformer.
The macro definition facility consists of these parts:
syntax-rules
special form.
The syntactic keyword of a macro may shadow variable bindings, and local variable bindings may shadow keyword bindings. All macros defined using the pattern language are "hygienic" and "referentially transparent" and thus preserve Scheme's lexical scoping:
2.11.1 Binding Constructs for Syntactic Keywords 2.11.2 Pattern Language 2.11.3 Syntactic Closures 2.11.4 Explicit Renaming
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
let-syntax
, letrec-syntax
, let*-syntax
and
define-syntax
are analogous to let
, letrec
,
let*
and define
, but they bind syntactic keywords to macro
transformers instead of binding variables to locations that contain
values.
((keyword transformer-spec) ...) |
Each keyword is an identifier, each transformer-spec is a a macro-transformer expression, and the body is a sequence of one or more expressions. It is an error for a keyword to appear more than once in the list of keywords being bound.
The expressions are expanded in the syntactic environment obtained
by extending the syntactic environment of the let-syntax
expression with macros whose keywords are the keywords, bound to
the specified transformers. Each binding of a keyword has the
expressions as its region.
(let-syntax ((when (syntax-rules () ((when test stmt1 stmt2 ...) (if test (begin stmt1 stmt2 ...)))))) (let ((if #t)) (when if (set! if 'now)) if)) => now (let ((x 'outer)) (let-syntax ((m (syntax-rules () ((m) x)))) (let ((x 'inner)) (m)))) => outer |
letrec-syntax
is the same as for let-syntax
.
The expressions are expanded in the syntactic environment obtained
by extending the syntactic environment of the letrec-syntax
expression with macros whose keywords are the keywords, bound to
the specified transformers. Each binding of a keyword has the
bindings as well as the expressions within its region, so
the transformers can transcribe expressions into uses of the macros
introduced by the letrec-syntax
expression.
(letrec-syntax ((my-or (syntax-rules () ((my-or) #f) ((my-or e) e) ((my-or e1 e2 ...) (let ((temp e1)) (if temp temp (my-or e2 ...))))))) (let ((x #f) (y 7) (temp 8) (let odd?) (if even?)) (my-or x (let temp) (if y) y))) => 7 |
let*-syntax
is the same as for let-syntax
.
The expressions are expanded in the syntactic environment obtained
by extending the syntactic environment of the letrec-syntax
expression with macros whose keywords are the keywords, bound to
the specified transformers. Each binding of a keyword has the
subsequent bindings as well as the expressions within its
region. Thus
(let*-syntax ((a (syntax-rules ...)) (b (syntax-rules ...))) ...) |
is equivalent to
(let-syntax ((a (syntax-rules ...))) (let-syntax ((b (syntax-rules ...))) ...)) |
The region of the binding introduced by define-syntax
is the
entire block in which it appears. However, the keyword may only
be used after it has been defined.
MIT Scheme permits define-syntax
to appear both at top level and
within lambda
bodies. The Revised^4 Report permits only
top-level uses of define-syntax
.
When compiling a program, a top-level instance of define-syntax
both defines the syntactic keyword and generates code that will redefine
the keyword when the program is loaded. This means that the same syntax
can be used for defining macros that will be used during compilation
and for defining macros to be used at run time.
Although macros may expand into definitions and syntax definitions in any context that permits them, it is an error for a definition or syntax definition to shadow a syntactic keyword whose meaning is needed to determine whether some form in the group of forms that contains the shadowing definition is in fact a definition, or, for internal definitions, is needed to determine the boundary between the group and the expressions that follow the group. For example, the following are errors:
(define define 3) (begin (define begin list)) (let-syntax ((foo (syntax-rules () ((foo (proc args ...) body ...) (define proc (lambda (args ...) body ...)))))) (let ((x 3)) (foo (plus x y) (+ x y)) (define foo x) (plus foo x))) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
MIT Scheme supports a high-level pattern language for specifying macro
transformers. This pattern language is defined by the Revised^4 Report
and is portable to other conforming Scheme implementations. To use the
pattern language, specify a transformer-spec as a
syntax-rules
form:
(pattern template) |
The pattern in a syntax-rule is a list pattern that begins with the keyword for the macro.
A pattern is either an identifier, a constant, or one of the following
(pattern ...) (pattern pattern ... . pattern) (pattern ... pattern ellipsis) |
and a template is either an identifier, a constant, or one of the following
(element ...) (element element ... . template) |
where an element is a template optionally followed by an ellipsis and an ellipsis is the identifier `...' (which cannot be used as an identifier in either a template or a pattern).
An instance of syntax-rules
produces a new macro transformer by
specifying a sequence of hygienic rewrite rules. A use of a macro whose
keyword is associated with a transformer specified by
syntax-rules
is matched against the patterns contained in the
syntax-rules, beginning with the leftmost syntax-rule. When
a match is found, the macro use is transcribed hygienically according to
the template.
An identifier that appears in the pattern of a syntax-rule is a pattern-variable, unless it is the keyword that begins the pattern, is listed in literals, or is the identifier `...'. Pattern variables match arbitrary input elements and are used to refer to elements of the input in the template. It is an error for the same pattern variable to appear more than once in a pattern.
The keyword at the beginning of the pattern in a syntax-rule is not involved in the matching and is not considered a pattern variable or literal identifier.
Identifiers that appear in literals are interpreted as literal identifiers to be matched against corresponding subforms of the input. A subform in the input matches a literal identifier if and only if it is an identifier and either both its occurrence in the macro expression and its occurrence in the macro definition have the same lexical binding, or the two identifiers are equal and both have no lexical binding.
A subpattern followed by ...
can match zero or more elements of
the input. It is an error for ...
to appear in literals.
Within a pattern the identifier ...
must follow the last element
of a nonempty sequence of subpatterns.
More formally, an input form F matches a pattern P if and only if:
(P_1 ... P_n)
and F is a
list of n forms that match P_1 through P_n,
respectively; or
(P_1 P_2 ... P_n
. P_n+1)
and F is a list or improper list of n or
more forms that match P_1 through P_n, respectively, and
whose nth "cdr" matches P_n+1; or
(P_1 ... P_n P_n+1
ellipsis)
where ellipsis is the identifier ...
and
F is a proper list of at least n forms, the first n of
which match P_1 through P_n, respectively, and each
remaining element of F matches P_n+1; or
equal?
procedure.
It is an error to use a macro keyword, within the scope of its binding, in an expression that does not match any of the patterns.
When a macro use is transcribed according to the template of the
matching syntax rule, pattern variables that occur in the template
are replaced by the subforms they match in the input. Pattern variables
that occur in subpatterns followed by one or more instances of the
identifier ...
are allowed only in subtemplates that are followed
by as many instances of ...
. They are replaced in the output by
all of the subforms they match in the input, distributed as indicated.
It is an error if the output cannot be built up as specified.
Identifiers that appear in the template but are not pattern variables or
the identifier ...
are inserted into the output as literal
identifiers. If a literal identifier is inserted as a free identifier
then it refers to the binding of that identifier within whose scope the
instance of syntax-rules
appears. If a literal identifier is
inserted as a bound identifier then it is in effect renamed to prevent
inadvertent captures of free identifiers.
(let ((=> #f)) (cond (#t => 'ok))) => ok |
The macro transformer for cond
recognizes =>
as a local variable, and hence an expression, and not as the
top-level identifier =>
, which the macro transformer treats
as a syntactic keyword. Thus the example expands into
(let ((=> #f)) (if #t (begin => 'ok))) |
instead of
(let ((=> #f)) (let ((temp #t)) (if temp ('ok temp)))) |
which would result in an invalid procedure call.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
MIT Scheme's syntax-transformation engine is an implementation of syntactic closures, a mechanism invented by Alan Bawden and Jonathan Rees. The main feature of the syntactic-closures mechanism is its simplicity and its close relationship to the environment models commonly used with Scheme. Using the mechanism to write macro transformers is somewhat cumbersome and can be confusing for the newly initiated, but it is easily mastered.
2.11.3.1 Syntax Terminology 2.11.3.2 Transformer Definition 2.11.3.3 Identifiers
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This section defines the concepts and data types used by the syntactic closures facility.
set!
special form is also a form. Examples of
forms:
17 #t car (+ x 4) (lambda (x) x) (define pi 3.14159) if define |
symbol?
. Macro transformers rarely distinguish symbols from
aliases, referring to both as identifiers. Another name for an
alias is synthetic identifier; this document uses both names.
A syntactic closure may be used in any context in which its form could
have been used. Since a syntactic closure is also a form, it may not
be used in contexts where a form would be illegal. For example, a form
may not appear as a clause in the cond
special form.
A syntactic closure appearing in a quoted structure is replaced by its form.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This section describes the special forms for defining syntactic-closures macro transformers, and the associated procedures for manipulating syntactic closures and syntactic environments.
sc-macro-transformer
expression, and the expanded expression is
evaluated in the transformer environment to yield a macro transformer as
described below. This macro transformer is bound to a macro keyword by
the special form in which the transformer
expression appears (for
example, let-syntax
).
In the syntactic closures facility, a macro transformer is a
procedure that takes two arguments, a form and a syntactic environment,
and returns a new form. The first argument, the input form, is
the form in which the macro keyword occurred. The second argument, the
usage environment, is the syntactic environment in which the input
form occurred. The result of the transformer, the output form, is
automatically closed in the transformer environment, which is the
syntactic environment in which the transformer
expression
occurred.
For example, here is a definition of a push
macro using
syntax-rules
:
(define-syntax push (syntax-rules () ((push item list) (set! list (cons item list))))) |
Here is an equivalent definition using sc-macro-transformer
:
(define-syntax push (sc-macro-transformer (lambda (exp env) (let ((item (make-syntactic-closure env '() (cadr exp))) (list (make-syntactic-closure env '() (caddr exp)))) `(set! ,list (cons ,item ,list)))))) |
In this example, the identifiers set!
and cons
are closed
in the transformer environment, and thus will not be affected by the
meanings of those identifiers in the usage environment env
.
Some macros may be non-hygienic by design. For example, the following
defines a loop
macro that implicitly binds exit
to an
escape procedure. The binding of exit
is intended to capture
free references to exit
in the body of the loop, so exit
must be left free when the body is closed:
(define-syntax loop (sc-macro-transformer (lambda (exp env) (let ((body (cdr exp))) `(call-with-current-continuation (lambda (exit) (let f () ,@(map (lambda (exp) (make-syntactic-closure env '(exit) exp)) body) (f)))))))) |
sc-macro-transformer
, except that the roles of the usage
environment and transformer environment are reversed. (Hence
RSC stands for Reversed Syntactic Closures.) In other
words, the procedure specified by expression still accepts two
arguments, but its second argument will be the transformer environment
rather than the usage environment, and the returned expression is closed
in the usage environment rather than the transformer environment.
The advantage of this arrangement is that it allows a simpler definition
style in some situations. For example, here is the push
macro
from above, rewritten in this style:
(define-syntax push (rsc-macro-transformer (lambda (exp env) `(,(make-syntactic-closure env '() 'SET!) ,(caddr exp) (,(make-syntactic-closure env '() 'CONS) ,(cadr exp) ,(caddr exp)))))) |
In this style only the introduced keywords are closed, while everything else remains open.
Note that rsc-macro-transformer
and sc-macro-transformer
are easily interchangeable. Here is how to emulate
rsc-macro-transformer
using sc-macro-transformer
. (This
technique can be used to effect the opposite emulation as well.)
(define-syntax push (sc-macro-transformer (lambda (exp usage-env) (capture-syntactic-environment (lambda (env) (make-syntactic-closure usage-env '() `(,(make-syntactic-closure env '() 'SET!) ,(caddr exp) (,(make-syntactic-closure env '() 'CONS) ,(cadr exp) ,(caddr exp))))))))) |
To assign meanings to the identifiers in a form, use
make-syntactic-closure
to close the form in a syntactic
environment.
make-syntactic-closure
constructs and returns a syntactic
closure of form in environment, which can be used anywhere
that form could have been used. All the identifiers used in
form, except those explicitly excepted by free-names,
obtain their meanings from environment.
Here is an example where free-names is something other than the
empty list. It is instructive to compare the use of free-names
in this example with its use in the loop
example above: the
examples are similar except for the source of the identifier being left
free.
(define-syntax let1 (sc-macro-transformer (lambda (exp env) (let ((id (cadr exp)) (init (caddr exp)) (exp (cadddr exp))) `((lambda (,id) ,(make-syntactic-closure env (list id) exp)) ,(make-syntactic-closure env '() init)))))) |
let1
is a simplified version of let
that only binds a
single identifier, and whose body consists of a single expression.
When the body expression is syntactically closed in its original
syntactic environment, the identifier that is to be bound by
let1
must be left free, so that it can be properly captured by
the lambda
in the output form.
In most situations, the free-names argument to
make-syntactic-closure
is the empty list. In those cases, the
more succinct close-syntax
can be used:
(make-syntactic-closure environment '() form) |
To obtain a syntactic environment other than the usage environment,
use capture-syntactic-environment
.
capture-syntactic-environment
returns a form that will, when
transformed, call procedure on the current syntactic environment.
Procedure should compute and return a new form to be transformed,
in that same syntactic environment, in place of the form.
An example will make this clear. Suppose we wanted to define a simple
loop-until
keyword equivalent to
(define-syntax loop-until (syntax-rules () ((loop-until id init test return step) (letrec ((loop (lambda (id) (if test return (loop step))))) (loop init))))) |
The following attempt at defining loop-until
has a subtle
bug:
(define-syntax loop-until (sc-macro-transformer (lambda (exp env) (let ((id (cadr exp)) (init (caddr exp)) (test (cadddr exp)) (return (cadddr (cdr exp))) (step (cadddr (cddr exp))) (close (lambda (exp free) (make-syntactic-closure env free exp)))) `(letrec ((loop (lambda (,id) (if ,(close test (list id)) ,(close return (list id)) (loop ,(close step (list id))))))) (loop ,(close init '()))))))) |
This definition appears to take all of the proper precautions to
prevent unintended captures. It carefully closes the subexpressions in
their original syntactic environment and it leaves the id
identifier free in the test
, return
, and step
expressions, so that it will be captured by the binding introduced by
the lambda
expression. Unfortunately it uses the identifiers
if
and loop
within that lambda
expression,
so if the user of loop-until
just happens to use, say, if
for the identifier, it will be inadvertently captured.
The syntactic environment that if
and loop
want to be
exposed to is the one just outside the lambda
expression: before
the user's identifier is added to the syntactic environment, but after
the identifier loop
has been added.
capture-syntactic-environment
captures exactly that environment
as follows:
(define-syntax loop-until (sc-macro-transformer (lambda (exp env) (let ((id (cadr exp)) (init (caddr exp)) (test (cadddr exp)) (return (cadddr (cdr exp))) (step (cadddr (cddr exp))) (close (lambda (exp free) (make-syntactic-closure env free exp)))) `(letrec ((loop ,(capture-syntactic-environment (lambda (env) `(lambda (,id) (,(make-syntactic-closure env '() `if) ,(close test (list id)) ,(close return (list id)) (,(make-syntactic-closure env '() `loop) ,(close step (list id))))))))) (loop ,(close init '()))))))) |
In this case, having captured the desired syntactic environment, it is
convenient to construct syntactic closures of the identifiers if
and the loop
and use them in the body of the
lambda
.
A common use of capture-syntactic-environment
is to get the
transformer environment of a macro transformer:
(sc-macro-transformer (lambda (exp env) (capture-syntactic-environment (lambda (transformer-env) ...)))) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
This section describes the procedures that create and manipulate
identifiers. The identifier data type extends the syntactic closures
facility to be compatible with the high-level syntax-rules
facility.
As discussed earlier, an identifier is either a symbol or an alias. An alias is implemented as a syntactic closure whose form is an identifier:
(make-syntactic-closure env '() 'a) => an alias |
Aliases are implemented as syntactic closures because they behave just
like syntactic closures most of the time. The difference is that an
alias may be bound to a new value (for example by lambda
or
let-syntax
); other syntactic closures may not be used this way.
If an alias is bound, then within the scope of that binding it is looked
up in the syntactic environment just like any other identifier.
Aliases are used in the implementation of the high-level facility
syntax-rules
. A macro transformer created by syntax-rules
uses a template to generate its output form, substituting subforms of
the input form into the template. In a syntactic closures
implementation, all of the symbols in the template are replaced by
aliases closed in the transformer environment, while the output form
itself is closed in the usage environment. This guarantees that the
macro transformation is hygienic, without requiring the transformer to
know the syntactic roles of the substituted input subforms.
#t
if object is an identifier, otherwise returns
#f
. Examples:
(identifier? 'a) => #t (identifier? (make-syntactic-closure env '() 'a)) => #t (identifier? "a") => #f (identifier? #\a) => #f (identifier? 97) => #f (identifier? #f) => #f (identifier? '(a)) => #f (identifier? '#(a)) => #f |
The predicate eq?
is used to determine if two identifers are
"the same". Thus eq?
can be used to compare identifiers
exactly as it would be used to compare symbols. Often, though, it is
useful to know whether two identifiers "mean the same thing". For
example, the cond
macro uses the symbol else
to identify
the final clause in the conditional. A macro transformer for
cond
cannot just look for the symbol else
, because the
cond
form might be the output of another macro transformer that
replaced the symbol else
with an alias. Instead the transformer
must look for an identifier that "means the same thing" in the usage
environment as the symbol else
means in the transformer
environment.
identifier=?
returns #t
if the meaning of
identifier1 in environment1 is the same as that of
identifier2 in environment2, otherwise it returns #f
.
Examples:
(let-syntax ((foo (sc-macro-transformer (lambda (form env) (capture-syntactic-environment (lambda (transformer-env) (identifier=? transformer-env 'x env 'x))))))) (list (foo) (let ((x 3)) (foo)))) => (#t #f) (let-syntax ((bar foo)) (let-syntax ((foo (sc-macro-transformer (lambda (form env) (capture-syntactic-environment (lambda (transformer-env) (identifier=? transformer-env 'foo env (cadr form)))))))) (list (foo foo) (foo bar)))) => (#f #t) |
Sometimes it is useful to be able to introduce a new identifier that is
guaranteed to be different from any existing identifier, similarly to
the way that generate-uninterned-symbol
is used.
This is implemented by syntactically closing identifier in a special empty environment.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Explicit renaming is an alternative facility for defining macro
transformers. In the MIT Scheme implementation, explicit-renaming
transformers are implemented as an abstraction layer on top of syntactic
closures. An explicit-renaming macro transformer is defined by an
instance of the er-macro-transformer
keyword:
er-macro-transformer
expression, and the expanded expression is
evaluated in the transformer environment to yield a macro transformer as
described below. This macro transformer is bound to a macro keyword by
the special form in which the transformer
expression appears (for
example, let-syntax
).
In the explicit-renaming facility, a macro transformer is a procedure that takes three arguments, a form, a renaming procedure, and a comparison predicate, and returns a new form. The first argument, the input form, is the form in which the macro keyword occurred.
The second argument to a transformation procedure is a renaming
procedure that takes the representation of an identifier as its
argument and returns the representation of a fresh identifier that
occurs nowhere else in the program. For example, the transformation
procedure for a simplified version of the let
macro might be
written as
(lambda (exp rename compare) (let ((vars (map car (cadr exp))) (inits (map cadr (cadr exp))) (body (cddr exp))) `((lambda ,vars ,@body) ,@inits))) |
This would not be hygienic, however. A hygienic let
macro must
rename the identifier lambda
to protect it from being captured by
a local binding. The renaming effectively creates an fresh alias for
lambda
, one that cannot be captured by any subsequent binding:
(lambda (exp rename compare) (let ((vars (map car (cadr exp))) (inits (map cadr (cadr exp))) (body (cddr exp))) `((,(rename 'lambda) ,vars ,@body) ,@inits))) |
The expression returned by the transformation procedure will be expanded in the syntactic environment obtained from the syntactic environment of the macro application by binding any fresh identifiers generated by the renaming procedure to the denotations of the original identifiers in the syntactic environment in which the macro was defined. This means that a renamed identifier will denote the same thing as the original identifier unless the transformation procedure that renamed the identifier placed an occurrence of it in a binding position.
The renaming procedure acts as a mathematical function in the sense that
the identifiers obtained from any two calls with the same argument will
be the same in the sense of eqv?
. It is an error if the renaming
procedure is called after the transformation procedure has returned.
The third argument to a transformation procedure is a comparison
predicate that takes the representations of two identifiers as its
arguments and returns true if and only if they denote the same thing in
the syntactic environment that will be used to expand the transformed
macro application. For example, the transformation procedure for a
simplified version of the cond
macro can be written as
(lambda (exp rename compare) (let ((clauses (cdr exp))) (if (null? clauses) `(,(rename 'quote) unspecified) (let* ((first (car clauses)) (rest (cdr clauses)) (test (car first))) (cond ((and (identifier? test) (compare test (rename 'else))) `(,(rename 'begin) ,@(cdr first))) (else `(,(rename 'if) ,test (,(rename 'begin) ,@(cdr first)) (cond ,@rest)))))))))) |
In this example the identifier else
is renamed before being passed
to the comparison predicate, so the comparison will be true if and
only if the test expression is an identifier that denotes the same
thing in the syntactic environment of the expression being transformed
as else
denotes in the syntactic environment in which the cond
macro was defined. If else
were not renamed before being passed to
the comparison predicate, then it would match a local variable that
happened to be named else
, and the macro would not be hygienic.
Some macros are non-hygienic by design. For example, the following
defines a loop
macro that implicitly binds exit
to an
escape procedure. The binding of exit
is intended to capture
free references to exit
in the body of the loop, so exit
is not renamed.
(define-syntax loop (er-macro-transformer (lambda (x r c) (let ((body (cdr x))) `(,(r 'call-with-current-continuation) (,(r 'lambda) (exit) (,(r 'let) ,(r 'f) () ,@body (,(r 'f))))))))) |
Suppose a while
macro is implemented using loop
, with the
intent that exit
may be used to escape from the while
loop. The while
macro cannot be written as
(define-syntax while (syntax-rules () ((while test body ...) (loop (if (not test) (exit #f)) body ...)))) |
because the reference to exit
that is inserted by the
while
macro is intended to be captured by the binding of
exit
that will be inserted by the loop
macro. In other
words, this while
macro is not hygienic. Like loop
, it
must be written using the er-macro-transformer
syntax:
(define-syntax while (er-macro-transformer (lambda (x r c) (let ((test (cadr x)) (body (cddr x))) `(,(r 'loop) (,(r 'if) (,(r 'not) ,test) (exit #f)) ,@body))))) |
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |