MzScheme's units are used to organize a program into
separately compilable and reusable components. A unit resembles a
procedure in that both are first-class values that are used for
abstraction. While procedures abstract over values in expressions,
units abstract over names in collections of definitions. Just as a
procedure is invoked to evaluate its expressions given actual
arguments for its formal parameters, a unit is invoked to evaluate
its definitions given actual references for its imported
variables. Unlike a procedure, however, a unit's imported variables
can be partially linked with the exported variables of another unit
prior to invocation. Linking merges multiple units together
into a single compound unit. The compound unit itself imports
variables that will be propagated to unresolved imported variables in
the linked units, and re-exports some variables from the linked units
for further linking.
In some ways, a unit resembles a module (see Chapter 5 in PLT MzScheme: Language Manual), but
units and modules serve different purposes overall. A unit
encapsulates a plugable component -- code that relies, for example, on
``some function f from a source to be determined later.'' In
contrast, if a module imports a function, the import is ``the
function f provided by the specific module m.'' Moreover,
a unit is a first-class value that can be multiply instantiated, each
time with different imports, whereas a module's context is
fixed. Finally, because a unit's interface is separate from its
implementation, units naturally support mutually recursive references
across unit boundaries, while module imports must be acyclic.
MzScheme supports two layers of units. The core unit system
comprises the unit, compound-unit, and
invoke-unit syntactic forms. These forms implement the basic
mechanics of units for separate compilation and linking. While the
semantics of units is most easily understood via the core forms, they
are too verbose for specifying the interconnections between units in
a large program. Therefore, a system of units with
signatures is provided on top of the core forms, comprising the
define-signature, unit/sig,
compound-unit/sig, and invoke-unit/sig syntactic
forms.
The core system is described in this chapter, and defined by the
unit.ss library. The signature system is described in
section 35, and defined by unitsig.ss. Details
about mixing core and signed units are presented in
section 35.9 (using procedures from unitsig.ss).
(unit
(importvariable···)
(exportexportage···)
unit-body-expr···)
exportage is one of
variable
(internal-variableexternal-variable)
The variables in the import clause are bound
within the unit-body-expr expressions. The variables for
exportages in the export clause must be defined
in the unit-body-exprs as described below; additional private
variables can be defined as well. The imported and exported variables
cannot occur on the left-hand side of an assignment (i.e., a
set! expression).
The first exportage form exports the binding defined as
variable in the unit body using the external name
variable. The second form exports the binding defined as
internal-variable using the external name
external-variable. The external variables from an
export clause must be distinct.
Each exported variable or internal-variable must be
defined in a define-values expression as a
unit-body-expr.11 All
identifiers defined by the unit-body-exprs together with the
variables from the import clause must be distinct.
In this example, the database@ unit implements the
database-searching part of the program, and the interface@
unit implements the graphical user interface. The database@
unit exports insert and lookup procedures to be
used by the graphical interface, while the interface@ unit
exports a show-message procedure to be used by the database
(to handle errors). The interface@ unit also imports
variables that will be supplied by an platform-specific graphics
toolbox.
The value of unit-expr must be a unit. For each of the unit's
imported variables, the invoke-unit expression must contain
an import-expr. The value of each import-expr is imported
into the unit. More detailed information about linking is provided in
the following section on compound units.
Invocation proceeds in two stages. First, invocation creates bindings
for the unit's private, imported, and exported variables. All
bindings are initialized to the undefined value. Second,
invocation evaluates the unit's private definitions and
expressions. The result of the last expression in the unit is the
result of the invoke-unit expression. The unit's exported
variable bindings are not accessible after the invocation.
These examples use the definitions from the earlier unit examples in
section 34.1.
The f1@ unit is invoked with no imports:
(invoke-unitf1@) ; => displays and returns the current time
Here is one way to invoke the database@ unit:
(invoke-unitdatabase@display)
This invocation links the imported variable message in
database@ to the standard Scheme display procedure,
sets up an empty database, and creates the procedures insert
and lookup tied to this particular database. Since the last
expression in the database@ unit is insert, the
invoke-unit expression returns the insert procedure
(without binding any top-level variables). The fact that
insert and lookup are exported is irrelevant to the
invocation; exports are only used for linking.
Invoking the database@ unit directly in the above manner is
actually useless. Although a program can insert information into the
database, it cannot extract information since the lookup
procedure is not accessible. The database@ unit becomes
useful when it is linked with another unit in a
compound-unit expression.
This form is similar to invoke-unit. However, instead of
returning the value of the unit's initialization expression,
define-values/invoke-unit expands to a
define-values expression that binds each identifier
export-id to the value of the corresponding variable exported
by the unit. At run time, if the unit does not export all of the
export-ids, the exn:unit exception is raised.
If prefix is specified, it must be either #f or an
identifier. If it is an identifier, the names defined by the
expansion of define-values/invoke-unit are prefixed with
prefix:.
This form is like define-values/invoke-unit, but the expansion
is a sequence of calls to namespace-variable-binding instead of a
define-values expression. Thus, when it is evaluated, a
namespace-variable-bind/invoke-unit expression binds top-level
variables in the current namespace.
The compound-unit form links several units into one new
compound unit. In the process, it matches imported variables in each
sub-unit either with exported variables of other sub-units or with
its own imported variables:
(compound-unit
(importvariable···)
(link (tag (sub-unit-exprlinkage···)) ···)
(export (tagexportage···) ···))
linkage is one of
variable
(tagvariable)
(tagvariable···)
exportage is one of
variable
(internal-variableexternal-variable)
tag is
identifier
The three parts of a compound-unit expression have the
following roles:
The import clause imports variables into the
compound unit. These imported variables are used as imports to the
compound unit's sub-units.
The link clause specifies how the compound unit
is created from sub-units. A unique tag is associated with each
sub-unit, which is specified using an arbitrary expression.
Following the unit expression, each linkage specifies a
variable using the variable form or the (tagvariable) form. In the former case, the variable must
occur in the import clause of the compound-unit
expression; in the latter case, the tag must be defined in the
same compound-unit expression. The (tagvariable···) form is a shorthand for multiple adjacent
clauses of the second form with the same tag.
The export clause re-exports variables from the
compound unit that were originally exported from the sub-units. The
tag part of each export sub-clause specifies the sub-unit
from which the re-exported variable is drawn. The exportages
specify the names of variables exported by the sub-unit to be
re-exported.
As in the export clause of the unit form, a re-exported
variable can be renamed for external references using the
(internal-variableexternal-variable) form. The
internal-variable is used as the name exported by the sub-unit,
and external-variable is the name visible outside the compound
unit.
The evaluation of a compound-unit expression starts with the
evaluation of the link clause's unit expressions (in
sequence). For each sub-unit, the number of variables it imports must
match the number of linkage specifications that are provided,
and each linkage specification is matched to an imported
variable by position. Each sub-unit must also export those variables
that are specified by the link and export clauses. If,
for any sub-unit, the number of imported variables does not agree
with the number of linkages provided, the exn:unit exception is raised. If an
expected exported variable is missing from a sub-unit for linking to
another sub-unit, the exn:unit exception is raised. If an expected export
variable is missing for re-export, the exn:unit exception is raised.
The invocation of a compound unit proceeds in two phases to invoke the
sub-units. In the first phase, the compound unit resolves the
imported variables of sub-units with the bindings provided for the
compound unit's imports and new bindings created for sub-unit
exports. In the second phase, the internal definitions and
expressions of the sub-units are evaluated sequentially according to
the order of the sub-units in the link clause. The result of
invoking a compound unit is the result from the invocation of the
last sub-unit.
These examples use the definitions from the earlier unit examples in
section 34.1.
The following compound-unit expression creates a (probably
useless) renaming wrapping around the unit bound to f2@:
(definef3@
(compound-unit
(import)
(link [A (f2@)])
(export (A (xA:x)))))
The only difference between f2@ and f3@ is that
f2@ exports a variable named x, while f3@
exports a variable named A:x.
The following example shows how the database@ and
interface@ units are linked together with a graphical
toolbox unit Graphics to produce a single, fully-linked
compound unit for the interactive phone book program.
This phone book program is executed with (invoke-unitprogram@). If (invoke-unitprogram@) is evaluated a second
time, then a new, independent database and window are created.
(unit?v) returns #t if v is a unit or #f
otherwise.
11 The detection of unit definitions is
the same as for internal definitions (see
section 2.8.5 in PLT MzScheme: Language Manual). Thus, the define and
define-struct forms can be used for definitions.
12 The ``@'' in the variable name ``f1@''
indicates (by convention) that its value is a unit.