Modules
MzScheme provides a module system for managing the scope of variable and syntax definitions, and for directing compilation. Module declarations can appear only at the top level. The space of module names is separate from the space of top-level variable and syntax names.
A module
declaration consists of the name for the module,
the name of a module to supply an initial set of syntax and variable
bindings, and a module body:
(module module-identifier initial-required-module-name body-datum ···)
A module encapsulates syntax definitions to be used in expanding the
body of the module, as well as expressions and definitions to be
evaluated when the module is executed. When a syntax identifier is
exported with provide
(as described in section 5.2),
its transformer can be used during the expansion of an importing
module; when a variable identifier is exported, its value can be used
(but not assigned with set!
) during the execution of an
importing module.
A module named mzscheme
is built in, and it exports the
procedures and syntactic forms described in R5RS and this
manual. The
module supplies the initial syntax and
variable bindings for a typical module.mzscheme
Example:
(module hello-world ; the module namemzscheme
; initial syntax and variable bindings ; for the module body ; the module body (display
"Hello world!") (newline
))
In general, the initial import serves as a kind of "language"
declaration. By initially importing a module other than
, a module can be defined in terms of a commonly-used
variant of Scheme that contains more than the MzScheme built-in
syntax and procedures, or a variant of Scheme that contains fewer
constructs. The initial import might even omit syntax for declaring
additional imports. For example, section 12.5 shows an
example module that defines a mzscheme
lambda-calculus
language.
5.1 Module Expansion and Execution
When a module declaration is evaluated, the module's body is
syntax-expanded and compiled, but not executed. The body is executed
only when the module is explicitly invoked, via a require
or
require-for-syntax
expression at the top level, or a call to
.dynamic-require
When a module is invoked, its body definitions and expressions are
evaluated. First, however, the definitions and expressions are
evaluated for each module imported (via require
) by the
invoked module. The import-initialization rule applies up the chain
of modules, so that every module used (directly or indirectly) by the
invoked module is executed before any module that uses its exports. A
module can only import from previously declared modules, so the
module-import relationship is acyclic.
Every module is executed at most once in response to an invocation, regardless of the number of times it is imported into other modules. Every top-level invocation executes only the modules needed by the invocation that have not been executed by previous invocations.
Example:
(module never-used ; unused modulemzscheme
(display
"This is never printed") (newline
)) (module hello-world-printer ; module used byhello-world2
mzscheme
(define (print-hello-world) (display
"Hello world!") (newline
)) (display
"printer ready") (newline
) (provide print-hello-world)) ; export (module hello-world2mzscheme
; initial import (require hello-world-printer) ; additional import (print-hello-world)) (require hello-world2) ; => prints"printer ready"
, then"Hello world!"
Separating module declarations from module executions benefits compilation in the presence of expressive syntax transformers, as explained in section 12.3.4.
5.2 Module Bodies
In general, the format of a module body depends on the initial
import. Since the
module defines the procedures and
syntactic forms described in R5RS and this manual, the
mzscheme
body-datum
s of a module using
as its initial
import must conform to the usual MzScheme top-level grammar.mzscheme
The require
form is used both to invoke a module at the
top level, and to import syntax and variables into a module.
(require require-spec ···) require-spec is one of module-name (only module-name identifier ···) (prefix prefix-identifier module-name) (all-except module-name identifier ···) (prefix-all-except prefix-identifier module-name identifier ···) (rename module-name local-identifier exported-identifier)
The module-name
form imports all exported identifiers from the
named module.
The (
form imports only the listed identifiers from the named module.
The only
module-name
identifier
···)(
form imports all identifiers from the named module, but locally
prefixes each identifier with prefix
prefix-identifier
module-name
)prefix-identifier
.
The (
form imports all identifiers from the named module, except for the
listed identifiers.
The all-except
module-name
identifier
···)(
form combines the prefix-all-except
prefix-identifier
module-name
identifier
···)prefix
and all-except
forms. Finally,
the (
imports rename
module-name
local-identifier
exported-identifier
)exported-identifier
from module-name
, binding it
locally to identifier
.
The provide
form (legal only within a module declaration)
exports syntax and variable bindings from the current module for use
by other modules. The exported identifiers must be either defined or
imported in the module, but the export of an identifier may precede
its definition or import.
(provide provide-spec ···) provide-spec is one of identifier (rename local-identifier export-identifier) (struct struct-identifier (field-identifier ···)) (all-from module-name) (all-from-except module-name identifier ···) (all-defined) (all-defined-except identifier ···) (prefix-all-defined prefix-identifier) (prefix-all-defined-except prefix-identifier identifier ···) (protect provide-spec ···)
The identifier
form exports the (imported or defined) identifier
from the module.
The (
form exports
rename
local-identifier
export-identifier
)local-identifier
from the module with the external name
export-identifier
; other modules importing from this one will
see export-identifier
instead of local-identifier
.
The (
form exports the names that
struct
struct-identifier
(field-identifier
···))(define-struct struct-identifier (field-identifier ···))
generates.
The (
form exports all of the identifiers imported from the named module,
using their local names.
The all-from
module-name
)(
form is similar, except
that the listed imported identifiers are not exported.
The all-from-except
module-name
identifier
···)(
form exports
all of the identifiers defined (not imported) in the module.
The all-defined
)(
form is similar, except that the listed
defined identifiers are not exported.
The all-defined-except
identifier
···)(
and
prefix-all-defined
prefix-identifier
)(
forms are like prefix-all-defined-except
prefix-identifier
identifier
···)all-defined
and all-defined-except
, but prefix-identifier
is prefixed onto each defined identifier for its external name.
The (
form is like the sequence of individual protect
provide-spec
···)provide-spec
s,
but the provided identifiers are protected (see section 9.4);
the provide-spec
s must not contain another protect
form,
an all-from
form, or an all-from-except
form, and they
must not name any identifier that is imported into the providing
module, instead of defined within the module.
The scope of all imported identifiers covers the entire module body,
as does the scope of any identifier defined within the module body.
See section 12.3.5 for additional information concerning
macro-generated definitions, require
declarations,
and provide
declarations. An identifier
can be
defined by a definition or import at most once, except than
an identifier
can be imported multiple times if each import is
from the same module. All exports must be unique. A module body
cannot contain free variables. A module is not permitted to mutate
an imported variable with set!
. However, mutations to an
exported variable performed by its defining module are visible to
modules that import the variable.
At syntax-expansion time, expressions and definitions within a module
are partially expanded, just enough to determine whether the
expression is a definition, syntax definition, import, export, or a
non-definition. If a partially expanded expression is a syntax
definition, the syntax transformer is immediately evaluated and the
syntax name is available for expanding successive expressions. Import
expressions are treated similarly, so that imported syntax is
available for expansion following its import. (The ordering of syntax
definitions does not affect the scope of the syntax names; a
transformer for A
can produce expressions containing
B
, while the transformer for B
produces expressions
containing A
, regardless of the order of declarations for
A
and B
. However, a syntactic form that produces
syntax definitions must be defined before it is used.) The
begin
form at the top level for a module body works like
begin
at the top level, so that the sub-expressions are
flattened out into the module's body.
At run time, expressions and definitions are evaluated in order as they appear within the module. Accessing a (non-syntax) identifier before it is initialized signals a run-time error, just like accessing an undefined global variable.
Example:
(module amzscheme
(provide x) (define x 1)) (module bmzscheme
(provide f (rename x y)) (define x 2) (define (f) (set! x 7))) (module cmzscheme
(require (prefix a. a) (prefix b. b)) (b.f) (display
(+ a.x b.y)) (newline
)) (require c) ; => executesc
, prints8
5.3 Modules and Macros
Macros defined with syntax-rules
follow the rules specified
in R5RS regarding the binding and free references in the macro
template. In particular, the template of an exported macro may refer
to an identifier defined in the module or imported into the module;
uses of the macro in other modules expand to references of the
identifier defined or imported at the macro-definition site, as
opposed to the use site. Uses of a macro in a module must not expand
to a set!
assignment of an identifier from any other module
(including the module that defines the macro).
Example:
(module amzscheme
(provide xm) (define y 2) (define-syntax xm ; a macro that expands toy
(syntax-rules () [(xm) y]))) (module bmzscheme
(require a) (printf
"~a~n" (xm))) (require b) ; => prints2
For further information about syntax definitions, see section 12.3.4. See section 12.6.5 for information on extracting details about an expanded or compiled module declaration. See section 9.4 for information on how unexported and protected identifiers in a macro expansion are constrained to their macro-introduced contexts.
5.4 Module Paths
In practice, the modules composing a program are rarely declared
together in a single file. Multiple module-declaring files can be
loaded in sequence with
, but modules that are intended as
libraries have complex interdependencies; constructing an appropriate
sequence of load
expressions -- one that loads each module
declaration exactly once and before all of its uses -- can be
difficult and tedious. Worse, even though module declarations prevent
collisions among syntax and variable names, module names themselves
can collide.load
To solve these problems, a module-name
can describe a path to a
module source file, which is resolved by the current module
name resolver. The default module name resolver loads the source for
a given module path the first time that the source is referenced. To
avoid module name collisions, the module in the referenced file is
assigned a name that identifies its source file.
A module path resolved by the standard resolver can take any of four forms:
unix-relative-path-string (file path-string) (lib filename-string collection-string ···) (planet . datum) path
When a module name is a string,
unix-relative-path-string
, it is interpreted as a path relative to the source of the containing module (as determined by
orcurrent-load-relative-directory
). Regardless of the platform running MzScheme, the path is always parsed as a Unix-format path: / is the path delimiter (multiple adjacent / are treated as a single delimiter), .. accesses the parent directory, and . accesses the current directory. To avoid portability problems, the path elements are further constrained to contain only alpha-numeric characters plus -, _, ., and space, and the path may not be empty or contain a leading or trailing slash.current-directory
When a module name has the form
(
, thenfile
path-string
)path-string
is interpreted as a file path using the current platform's path conventions. Ifpath-string
is a relative path, it is resolved relative to the source of the containing module (as determined by
orcurrent-load-relative-directory
).current-directory
When a module name has the form
(
, it specifies a collection-based library; see Chapter 16 for more information about libraries and collections.lib
filename-string
collection-string
···)When a module name has the form
(
, it is passed to the PLaneT resolver as described in section 5.4.1.planet
.datum
)Since path values (see section 11.3.1) cannot be written as literal syntax, a
path
never appears inrequire
forms. However, an absolute path value may be passed todynamic-require
, and it is treated in the same way as afile
form.
A source file that is referenced by a module path must contain a single module declaration. The name of the declared module must match the source's filename, minus its suffix.
Different module paths can access the same module, but for the
purposes of provide
declarations using all-from
and
all-from-except
, source module paths are compared
syntactically (instead of comparing resolved module names).
5.4.1 Module Name Resolver
In general, the module name resolver is invoked by MzScheme when a
module-name
is not an identifier. The grammar of non-symbolic
module names is determined by the module name resolver. The module
name resolver, in turn, is determined by the
parameter (see also
section 7.9.1.12). The resolver is a function
that takes three arguments -- an arbitrary value for the module
path, a symbol for the source module's name, and a syntax object or
current-module-name-resolver
#f
-- and returns a symbol for the resolved name.
Except for (planet . datum)
paths (which are handled as
described below), the standard module name resolver creates a module
identifier as the expanded, simplified, case-normalized, and
de-suffixed path of the file designated by the module path. (See
section 11.3 for details on platform-specific path handling.)
To better support dynamic-require
, the standard module name
resolver accepts a path object (see section 11.3.1) and treats it
like a file
module path.
The standard module name resolver keeps a per-registry table of loaded
module identifiers (where the registry is obtained from a namespace;
see Chapter 8). If the resolved identifier is not in the
table, the identifier is put into the table and the corresponding
file is loaded with a variant of
that
passes the expected module name to the load handler.load/use-compiled
While loading a file, the standard resolver sets the
parameter, so that the name of
any module declared in the loaded file is given a prefix. This
mechanism enables the resolver to avoid module name collisions. The
resolver sets the prefix to the resolved module name, minus the
de-suffixed file name. It also loads the file by calling the load
handler or load extension handler with the name of the expected
module (see section 5.8).current-module-name-prefix
The current module name resolver is called by
namespace-attach-module
to notify the resolver that a
module was attached to the current namespace (and shouldn't be loaded
in the future for the namespace's registry). In this notification
mode, the first argument to the resolver is #f
, the second
argument is the name of the attached module, and the third argument
is #f
.
When the default module name resolver is given a module path of the
form (planet . datum)
as its first argument, it provides
all of the resolver arguments to the PLaneT resolver. If the PLaneT
resolver has not yet been loaded, it is loaded in the initial
namespace by requiring planet-module-name-resolver
from
(lib "resolver.ss" "planet")
. Thereafter, the PLaneT
resolver receives each namespace-attach-module
notification
that the standard resolver receives.
5.4.2 Module Names and Compilation
When syntax-expanding or compiling a module
declaration,
MzScheme resolves module names for imports (since some imported
identifier may have syntax bindings), but it also preserves the
module path name. Consequently, a compiled module can be moved to
another filesystem, where the module name resolver can resolve
inter-module references among compiled code.
5.5 Dynamic Module Access
(dynamic-require
module-path-v provided-symbol
)
dynamically
invokes the module specified by module-path-v
in the current
namespace's registry if it is not yet invoked. If module-path-v
is not a symbol, the current module name resolver may load a module
declaration to resolve it. For example, the default module-name
resolver accepts a path value as module-path-v
. The path is not
resolved with respect to any other module, even if the current
namespace corresponds to a module body.
If provided-symbol
is #f
, then the result is
void. Otherwise, when provided-symbol
is a symbol, the
value of the module's export with the given name is returned. If the
module has no such exported variable or if the variable is protected
(see section 9.4), the exn:fail:contract
exception is raised. The
expansion-time portion of the module is not executed.
If provided-symbol
is void, then the module is partially
invoked, where its expansion-time expressions are evaluated, but not
its normal expressions (though the module may have been invoked
previously in the current namespace's registry). The result is
void.
(dynamic-require-for-syntax
module-path-v provided-symbol-or-#f
)
is similar to
dynamic-require
, except that it accesses a value from an
expansion-time module instance (the one that could be used by
transformers in expanding top-level expressions in the current
namespace). As with dynamic-require
, the module
name resolver may load a module declaration to resolve
module-path-v
if it is not a symbol.
5.6 Re-declaring Modules
When a module is re-declared in a namespace whose registry already
contains a declaration of the module (see Chapter 8), the
new declaration's syntax and variable definitions replace and extend
the old declarations. If a variable in the old declaration has no
counterpart in the new declaration, it continues to exist, but
becomes inaccessible to newly compiled code. In other words, a module
name in a particular registry maps to a namespace containing the
module body's definitions; see also module->namespace
in
section 8.3.
If a module is invoked before it is re-declared, each re-declaration of the module is immediately invoked. The immediate invocation is necessary to keep the module-specific namespace consistent with the module declaration.
A module can only be redeclared when the current code inspector -- as
determined by the
parameter (see
section 7.9.1.8) -- controls the invocation of the
module in the current namespace's registry. If the current code
inspector does not control the invocation at the time of a
re-declaration attempt, the current-code-inspector
exn:fail:contract
exception is raised. If a module
re-declaration would create an import cycle, the
exn:fail:contract
exception is raised.
5.7 Built-in Modules
The built-in
module is implemented by several
primitive modules whose names start with mzscheme
#%
. In general,
module names starting with #%
are reserved for use by
MzScheme and embedding applications. The built-in modules are
declared in the initial namespace's registry via
namespace-attach-module
, so they cannot be re-declared and
their private namespaces are not available via
module->namespace
.
5.8 Modules and Load Handlers
The second argument to a load handler or load extension handler
indicates whether the load is expected (and required) to produce a
module declaration. If the second argument is #f
, the file
is loaded normally, otherwise the argument will be a symbol and the
file must be checked specially before it is loaded.
When the second argument to the local handler is a symbol, the handler
is responsible for ensuring that the file-to-load actually contains a
module
declaration (possibly compiled); if not, it must
raise an exception without evaluating the declaration. The handler
must also raise an exn:fail
exception if the name in the
module declaration is not the same as the symbol argument to the
handler (before applying any prefix in
).current-module-name-prefix
Furthermore, while reading the file and expanding the module declaration, the load handler must set reader parameter values (see section 7.9.1.3) to the following states:
(read-case-sensitive
#t) (read-square-bracket-as-paren
#t) (read-curly-brace-as-paren
#t) (read-accept-box
#t) (read-accept-compiled
#t) (read-accept-bar-quote
#t) (read-accept-graph
#t) (read-decimal-as-inexact
#t) (read-accept-dot
#t) (read-accept-quasiquote
#t) (read-accept-reader
#t)
These states are the same as the normal defaults, except that
compiled-code reading is enabled. Note that a module body can be made
case sensitive by prefixing the module with #cs
(see
section 11.2.4).
Finally, before compiling or evaluating a module declaration from
source, the handler must replace a leading module
identifier
with an identifier that is bound to the module
export of
MzScheme. Evaluating the expression will then produce a module
declaration, regardless of the binding of module
in the
current namespace.
Separate compilation of module
declarations introduces the
possibility of import cycles when the module declarations are
executed. The exn:fail
exception is raised when such a cycle is detected.