Next: Static type system, Previous: Module configuration language, Up: Module system
One reason that the standard Scheme language does not support a module system yet is the issue of macros and modularity. There are several issues to deal with:
Scheme48's module system tries to address all of these issues coherently and comprehensively. Although it cannot offer total separate compilation, it can offer incremental compilation, and compiled modules can be dumped to the file system & restored in the process of incremental compilation.1
Scheme48's module system is also very careful to preserve non-local
module references from a macro's expansion. Macros in Scheme48 are
required to perform hygienic renaming in order for this preservation,
however; see Explicit renaming macros. For a brief example,
consider the delay
syntax for lazy evaluation. It expands to a
simple procedure call:
(delay expression) ==> (make-promise (lambda () expression))
However, make-promise
is not exported from the scheme
structure. The expansion works correctly due to the hygienic renaming
performed by the delay
macro transformer: when it hygienically
renames make-promise
, the output contains not the symbol but a
special token that refers exactly to the binding of make-promise
from the environment in which the delay
macro transformer was
defined. Special care is taken to preserve this information. Had
delay
expanded to a simple S-expression with simple symbols, it
would have generated a free reference to make-promise
, which
would cause run-time undefined variable errors, or, if the module in
which delay
was used had its own binding of or imported a
binding of the name make-promise
, delay
's expansion
would refer to the wrong binding, and there could potentially be
drastic and entirely unintended impact upon its semantics.
Finally, Scheme48's module system has a special design for the tower of phases, called a reflective tower.2 Every storey represents the environment available at successive macro levels. That is, when the right-hand side of a macro definition or binding is evaluated in an environment, the next storey in that environment's reflective tower is used to evaluate that macro binding. For example, in this code, there are two storeys used in the tower:
(define (foo ...bar...) (let-syntax ((baz ...quux...)) ...zot...))
In order to evaluate code in one storey of the reflective tower, it is
necessary to expand all macros first. Most of the code in this example
will eventually be evaluated in the first storey of the reflective
tower (assuming it is an ordinary top-level definition), but, in order
to expand macros in that code, the let-syntax
must be expanded.
This causes ...quux...
to be evaluated in the second
storey of the tower, after which macro expansion can proceed, and long
after which the enclosing program can be evaluated.
The module system provides a simple way to manipulate the reflective
tower. There is a package clause, for-syntax
, that simply
contains package clauses for the next storey in the tower. For
example, a package with the following clauses:
(open scheme foo bar) (for-syntax (open scheme baz quux))
has all the bindings of scheme
, foo
, & bar
, at the
ground storey; and the environment in which macros' definitions are
evaluated provides everything from scheme
, baz
, &
quux
.
With no for-syntax
clauses, the scheme
structure is
implicitly opened; however, if there are for-syntax
clauses,
scheme
must be explicitly opened.3 Also, for-syntax
clauses
may be arbitrarily nested: reflective towers are theoretically infinite
in height. (They are internally implemented lazily, so they grow
exactly as high as they need to be.)
Here is a simple, though contrived, example of using for-syntax
.
The while-loops
structure exports while
, a macro similar
to C's while
loop. While
's transformer unhygienically
binds the name exit
to a procedure that exits from the loop.
It necessarily, therefore, uses explicit renaming macros in order to break hygiene; it also, in the
macro transformer, uses the destructure
macro to destructure the
input form (see Library utilities, in particular, the structure
destructuring
for destructuring S-expressions).
(define-structure while-loops (export while) (open scheme) (for-syntax (open scheme destructuring)) (begin (define-syntax while (lambda (form r compare) (destructure (((WHILE test . body) form)) `(,(r 'CALL-WITH-CURRENT-CONTINUATION) (,(r 'LAMBDA) (EXIT) (,(r 'LET) (r 'LOOP) () (,(r 'IF) ,test (,(r 'BEGIN) ,@body (,(r 'LOOP))))))))) (CALL-WITH-CURRENT-CONTINUATION LAMBDA LET IF BEGIN))))
This next while-example
structure defines an example procedure
foo
that uses while
. Since while-example
has no
macro definitions, there is no need for any for-syntax
clauses;
it imports while
from the while-loops
structure only at
the ground storey, because it has no macro bindings to evaluate the
transformer expressions of:
(define-structure while-example (export foo) (open scheme while-loops) (begin (define (foo x) (while (> x 9) (if (integer? (sqrt x)) (exit (expt x 2)) (set! x (- x 1)))))))
[1] While such facilities are not built-in to Scheme48, there is a package to do this, which will probably be integrated at some point soon into Scheme48.
[2] This would be more accurately named `syntactic tower,' as it has nothing to do with reflection.
[3] This is actually only in the default config package of the default development environment. The full mechanism is very general.