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 mzscheme module supplies the initial syntax and variable bindings for a typical module.

Example:

(module hello-world    ; the module name 
        mzscheme       ; 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 mzscheme, 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 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 module 
        mzscheme 
  (display "This is never printed") 
  (newline)) 

(module hello-world-printer      ; module used by hello-world2
        mzscheme 
  (define (print-hello-world) 
    (display "Hello world!") 
    (newline)) 
  (display "printer ready") 
  (newline) 
  (provide print-hello-world))   ; export

(module hello-world2 
        mzscheme                 ; 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 mzscheme module defines the procedures and syntactic forms described in R5RS and this manual, the body-datums of a module using mzscheme as its initial import must conform to the usual MzScheme top-level grammar.

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 (only module-name identifier ···) form imports only the listed identifiers from the named module. The (prefix prefix-identifier module-name) form imports all identifiers from the named module, but locally prefixes each identifier with prefix-identifier. The (all-except module-name identifier ···) form imports all identifiers from the named module, except for the listed identifiers. The (prefix-all-except prefix-identifier module-name identifier ···) form combines the prefix and all-except forms. Finally, the (rename module-name local-identifier exported-identifier) imports 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 (rename local-identifier export-identifier) form exports 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 (struct struct-identifier (field-identifier ···)) form exports the names that (define-struct struct-identifier (field-identifier ···)) generates. The (all-from module-name) form exports all of the identifiers imported from the named module, using their local names. The (all-from-except module-name identifier ···) form is similar, except that the listed imported identifiers are not exported. The (all-defined) form exports all of the identifiers defined (not imported) in the module. The (all-defined-except identifier ···) form is similar, except that the listed defined identifiers are not exported. The (prefix-all-defined prefix-identifier) and (prefix-all-defined-except prefix-identifier identifier ···) forms are like all-defined and all-defined-except, but prefix-identifier is prefixed onto each defined identifier for its external name.

The (protect provide-spec ···) form is like the sequence of individual provide-specs, but the provided identifiers are protected (see section 9.4); the provide-specs 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 a mzscheme 
  (provide x) 
  (define x 1)) 
 
(module b mzscheme 
  (provide f (rename x y)) 
  (define x 2) 
  (define (f) (set! x 7))) 
 
(module c mzscheme 
  (require (prefix a. a) (prefix b. b)) 
  (b.f) 
  (display (+ a.x b.y)) 
  (newline)) 
 
(require c)          ; => executes c, prints 8

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 a mzscheme 
  (provide xm) 
  (define y 2) 
  (define-syntax xm     ; a macro that expands to y
    (syntax-rules () 
      [(xm) y]))) 
 
(module b mzscheme 
  (require a) 
  (printf "~a~n" (xm))) 
 
(require b)   ; => prints 2

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 load, 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.

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

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 current-module-name-resolver parameter (see also section 7.9.1.12). The resolver is a function that takes one, three, and four arguments:

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, and #f is not provided as the module name resolver's fourth argument, then the identifier is put into the table and the corresponding file is loaded with a variant of load/use-compiled that passes the expected module name to the load handler.

While loading a file, the standard resolver sets the current-module-name-prefix 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).

Module loading is supressed (i.e., #f is supplied as a fourth argument to the module name resolver) when resolving module paths in syntax objects (see section 12.2). When a syntax object is manipulated, the current namespace might not match the original namespace for the syntax object, and the module should not necessarily be loaded in the current namespace.

The current module name resolver is called with a single argument by namespace-attach-module to notify the resolver that a module was attached to the current namespace (and should not be loaded in the future for the namespace's registry). No other MzScheme operation invokes the module name resolver with a single argument, but other tools (such as DrScheme) might call this resolver in this mode to avoid redundant module loads.

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 is called for every one-argument call to the default module name resolver, in addition to calls for handle (planet . datum) paths.

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.

When a module re-declaration implies invocation, the invocation can fail at the definition of a binding that was constant in the original module (where any definition without a set! within the module counts as a constant definition); preventing re-definition protects potential optimizations (for the original declaration) that rely on constant bindings. Set the compile-enforce-module-constants parameter (see section 7.9) to #f to disable optimizations that rely on constant bindings and to allow unrestrcted re-definition of module bindings. To enable re-definition, the compile-enforce-module-constants parameter must be set before the original declaration of the module.

In addition to the constraint on constant definitions, a module can be redeclared only when the current code inspector -- as determined by the current-code-inspector 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 exn:fail:contract exception is raised.

5.7  Built-in Modules

The built-in mzscheme module is implemented by several primitive modules whose names start with #%. 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.