The unit syntax presented in section 34 poses a serious
notational problem: each variable that is imported or exported must
be separately enumerated in many import, export, and
link clauses. Consider the phone book program example from
section 34.3: a realistic graphics@ unit would
contain many more procedures than make-window and
make-button, and it would be unreasonable to enumerate the
entire graphics toolbox in every client module. Future extensions to
the graphics library are likely, and while the program must certainly
be re-compiled to take advantage of the changes, the programmer
should not be required to change the program text in every place that
the graphics library is used.
This problem is solved by separating the specification of a unit's
signature (or ``interface'') from its implementation. A
unit signature is essentially a list of variable names. A signature can
be used in an import clause, an export clause, a link clause, or an
invocation expression to import or link a set of variables at
once. Signatures clarify the connections between units, prevent
mis-orderings in the specification of imported variables, and provide
better error messages when an illegal linkage is specified.
Signatures are used to create units with signatures, a.k.a.
signed units. Signatures and signed units are used
together to create signed compound units. As in the core
system, a signed compound unit is itself a signed unit.
Signed units are first-class values, just like their counterparts in
the core system. A signature is not a value. However, signature
information is bundled into each signed unit value so that
signature-based checks can be performed at run time (when signed
units are linked and invoked).
Along with its signature information, a signed unit includes a
primitive unit from the core system that implements the signed
unit. This underlying unit can be extracted for mixed-mode programs
using both signed and unsigned units. More importantly, the semantics
of signed units is the same as the semantics for regular units; the
additional syntax only serves to specify signatures and to check
signatures for linking.
The unit/sig form creates a signed unit in the same way
that the unit form creates a unit in the core system. The only
difference between these forms is that signatures are used to specify
the imports and exports of a signed unit.
In the primitive unit form, the import clause only
determines the number of variables that will be imported when the
unit is linked; there are no explicitly declared connections between
the import variables. In contrast, a unit/sig form's
import clause does not specify individual variables; instead,
it specifies the signatures of units that will provide its imported
variables, and all of the variables in each signature are
imported. The ordered collection of signatures used for importing in
a signed unit is the signed unit's import signature.
Although the collection of variables to be exported from a
unit/sig expression is specified by a signature rather than an
immediate sequence of variables,13 variables are exported
in a unit/sig form in the same way as in the unit
form. The export signature of a signed unit is the
collection of names exported by the unit.
In this program fragment, the signed unit gravity@ imports a
collection of arithmetic procedures, a collection of calculus
procedures, and a collection of graphics procedures. The arithmetic
collection will be provided through a signed unit that matches the
arithmetic^ (export) signature, while the graphics collection
will be provided through a signed unit that matches the
graphics^ (export) signature. The gravity@ signed
unit itself has the export signature gravity^.
Suppose that the procedures in graphics^ were named
add and remove rather than add-pixel and
remove-pixel. In this case, the gravity@ unit
cannot import both the arithmetic^ and graphics^
signatures as above, because the name add would be ambiguous
in the unit body. To solve this naming problem, the imports of a
signed unit can be distinguished by providing prefix tags:
A signature is either a signature description or a bound
signature identifier:
(sig-element···)
signature-identifiersig-element is one of
variable
(structbase-identifier (field-identifier···) omission···)
(open signature)
(unitidentifier:signature)
omission is one of
-selectors-setters
(- variable)
Together, the element descriptions determine the set of elements that
compose the signature:
The simple variable form adds a variable name to the new
signature.
The struct form expands into the list of variable
names generated by a define-struct expression with the given
base-identifier and field-identifiers.
The actual structure type can contain additional fields; if a field
identifier is omitted, the corresponding selector and setter names
are not added to the signature. Optional omission
specifications can omit other kinds of names: -selectors omits
all field selector variables. -setters omits all field setter
variables, and (-variable) omits a specific generated
variable.
In a unit importing the signature, the base-identifier is also
bound to expansion-time information about the structure type (see
section 12.6.3 in PLT MzScheme: Language Manual). The expansion-time information records the
descriptor, constructor, predicate, field accessor, and field mutator
bindings from the signature. It also indicates that the accessor and
mutator sets are potentially incomplete (so match works with
the structure type, but shared does not), either because the
signature omits fields, or because the strcuture type is derived from
a base type (which cannot be declared in a signature, currently).
The open form copies all of the elements of another
signature into the new signature description.
The unit form creates a sub-signature within the new
signature. A signature that includes a unit clause corresponds
to a signed compound unit that exports an embedded unit. (Embedded
units are described in section 35.6 and
section 35.7.)
The names of all elements in a signature must be
distinct.14 Two signatures match when they contain
the same element names, and when a name in both signatures is either
a variable name in both signatures or a sub-signature name in both
signatures such that the sub-signatures match. The order of elements
within a signature is not important. A source signature
satisfies a destination signature when the source signature
has all of the elements of the destination signature, but the source
signature may have additional elements.
The define-signature form binds a signature to an
identifier:
(define-signaturesignature-identifiersignature)
The let-signature form binds a signature to an identifier
within a body of expressions:
(let-signatureidentifiersignaturebody-expr···1)
For various purposes, signatures must be flattened into a linear
sequence of variables. The flattening operation is defined as
follows:
All variable name elements of the signature are included in
the flattened signature.
For each sub-signature element named s, the sub-signature
is flattened, and then each variable name in the flattened
sub-signature is prefixed with s: and included in the
flattened signature.
(unit/sigsignature
(importimport-element···)
renamesunit-body-expr···)
import-element is one of
signature
(identifier:signature)
renames is either empty or
(rename (internal-variablesignature-variable) ···)
The signature immediately following unit/sig specifies the
export signature of the signed unit. This signature cannot contain
sub-signatures. Each element of the signature must have a
corresponding variable definition in one of the
unit-body-exprs, modulo the optional rename clause. If
the rename clause is present, it maps internal-variables
defined in the unit-body-exprs to signature-variables in
the export signature.
The import-elements specify imports for the signed unit. The
names bound within the signed-unit-body-exprs to imported
bindings are constructed by flattening the signatures according to
the algorithm in section 35.2:
For each import-element using the signature form,
the variables in the flattened signature are bound in the
signed-unit-body-exprs.
For each import-element using the (identifier
: signature) form, the variables in the flattened signature
are prefixed with identifier: and the prefixed variables
are bound in the signed-unit-body-exprs.
The compound-unit/sig form links signed units into a
signed compound unit in the same way that the compound-unit
form links primitive units. In the compound-unit/sig form,
signatures are used for importing just as in unit/sig (except
that all import signatures must have a tag), but the use of
signatures for linking and exporting is more complex.
Within a compound-unit/sig expression, each unit to be linked is
represented by a tag. Each tag is followed by a signature and an
expression. A tag's expression evaluates (at link-time) to a signed
unit for linking. The export signature of this unit must
satisfy the tag's signature. ``Satisfy'' does not mean
``match exactly''; satisfaction requires that the unit exports at
least the variables specified in the tag's signature, but the unit
may actually export additional variables. Those additional variables
are ignored for linking and are effectively hidden by the compound
unit.
To specify the compound unit's linkage, an entire unit is provided
(via its tag) for each import of each linked unit. The number of
units provided by a linkage must match the number of signatures
imported by the linked unit, and the tag signature for each provided
unit must match (exactly) the corresponding imported signature.
The following example shows the linking of an arithmetic unit, a
calculus unit, a graphics unit, and a gravity modeling unit:
In the compound-unit/sig expression for model@, all
link-time signature checks succeed since, for example,
arithmetic@ does indeed implement arithmetic^ and
gravity@ does indeed import units with the
arithmetic^, calculus^, and graphics^ signatures.
The export signature of a signed compound unit is implicitly specified
by the export clause. In the above example, the model@
compound unit exports a go variable, so its export signature is
the same as gravity^. More forms for exporting are described
in section 35.6.
As explained in section 35.4, the signature checking for a
linkage requires that a provided signature exactly matches the
corresponding import signature. At first glance, this requirement
appears to be overly strict; it might seem that the provided
signature need only satisfy the imported signature. The reason
for requiring an exact match at linkages is that a
compound-unit/sig expression is expanded into a
compound-unit expression. Thus, the number and order of the
variables used for linking must be fully known at compile time.
The exact-match requirement does not pose any obstacle as long as a
unit is linked into only one other unit. In this case, the signature
specified with the unit's tag can be contrived to match the importing
signature. However, a single unit may need to be linked into
different units, each of which may use different importing
signatures. In this case, the tag's signature must be ``bigger'' than
both of the uses, and a restricting signature is explicitly
provided at each linkage. The tag must satisfy every restricting
signature (this is a syntactic check), and each restricting signature
must exactly match the importing signature (this is a run-time
check).
In the example from section 35.4, both calculus@ and
gravity@ import numerical procedures, so both import the
arithmetic^ signature. However, calculus@ does not
actually need the power procedure to implement integrate;
therefore, calculus@ could be as effectively implemented in
the following way:
Now, the old compound-unit/sig expression for model@
no longer works. Although the old expression is still syntactically
correct, link-time signature checking will discover that
calculus@ expects an import matching the signature
simple-arithmetic^ but it was provided a linkage with the
signature arithmetic^. On the other hand, changing the
signature associated with ARITHMETIC to
simple-arithmetic^ would cause a link-time error for the
linkage to gravity@, since it imports the
arithmetic^ signature.
The solution is to restrict the signature of ARITHMETIC in
the linkage for CALCULUS:
A syntactic check will ensure that arithmetic^ satisfies
simple-arithmetic^ (i.e., arithmetic^ contains at
least the variables of simple-arithmetic^). Now, all
link-time signature checks will succeed, as well.
Signed compound units can re-export variables from linked units in the
same way that core compound units can re-export variables. The
difference in this case is that the collection of variables that are
re-exported determines an export signature for the compound
unit. Using certain export forms, such as the open form
instead of the var form (see section 35.7),
makes it easier to export a number of variables at once, but these
are simply shorthand notations.
Signed compound units can also export entire units as well as
variables. Such an exported unit is an embedded unit of the
compound unit. Extending the example from section 35.5, the
entire gravity@ unit can be exported from model@
using the unit export form:
The export signature of model@ no longer matches
gravity^. When a compound unit exports an embedded unit, the
export signature of the compound unit has a sub-signature that
corresponds to the full export signature of the embedded unit. The
following signature, model^, is the export signature for the
revised model@:
(define-signaturemodel^ ((unitGRAVITY:gravity^)))
The signature model^ matches the (implicit) export signature
of model@ since it contains a sub-signature named
GRAVITY -- matching the tag used to export the gravity@
unit -- that matches the export signature of gravity@.
The export form (unitGRAVITY) does not export any variable
other than gravity@'s go, but the ``unitness'' of
gravity@ is intact. The embedded GRAVITY unit is
now available for linking when model@ is linked to other
units.
The compound-unit/sig form links multiple signed units
into a new signed compound unit:
(compound-unit/sig
(import (tag:signature) ···)
(link (tag:signature (exprlinkage···)) ···)
(exportexport-element···))
linkage is
unit-pathunit-path is one of
simple-unit-path
(simple-unit-path:signature)
simple-unit-path is one of
tag
(tagidentifier···)
export-element is one of
(var (simple-unit-pathvariable))
(var (simple-unit-pathvariable) external-variable)
(open unit-path)
(unitunit-path)
(unitunit-pathvariable)
tag is
identifier
The import clause is similar to the import clause
of a unit/sig expression, except that all imported signatures
must be given a tag identifier.
The link clause of a compound-unit/sig expression is
different from the link clause of a compound-unit expression
in two important aspects:
Each sub-unit tag is followed by a signature. This signature
corresponds to the export signature of the signed unit that will be
associated with the tag.
The linkage specification consists of references to entire
signed units rather than to individual variables that are exported by
units. A referencing unit-path has one of four forms:
The tag form references an imported unit or another
sub-unit.
The (tag : signature) form references an
imported unit or another sub-unit, and then restricts the effective
signature of the referenced unit to signature.
The (tagidentifier···) references an
embedded unit within a signed compound unit. The signature for the
tag unit must contain a sub-signature that corresponds to the
embedded unit, where the sub-signature's name is the initial
identifier. Additional identifiers trace a path into
nested sub-signatures to a final embedded unit. The degenerate
(tag) form is equivalent to tag.
The ((tagidentifier···) : signature)
form is like the (tagidentifier···) form except the effective signature of the referenced unit
is restricted to signature.
The export clause determines which variables in the sub-units
are re-exported and implicitly determines the export signature of the
new compound unit. A signed compound unit can export both individual
variables and entire signed units. When an entire signed unit is
exported, it becomes an embedded unit of the resulting compound unit.
There are five different forms for specifying exports:
The (var (unit-pathvariable)) form exports
variable from the unit referenced by unit-path. The
export signature for the signed compound unit includes a
variable element.
The (var (unit-pathvariable) external-variable)
form exports variable from the unit referenced by
unit-path. The export signature for the signed compound unit
includes an external-variable element.
The (open unit-path) form exports variables and
embedded units from the referenced unit. The collection of variables
that are actually exported depends on the effective
signature of the referenced unit:
If unit-path includes a signature restriction, then
only elements from the restricting signature are exported.
Otherwise, if the referenced unit is an embedded unit,
then only the elements from the associated sub-signature are exported.
Otherwise, unit-path is just tag; in this case,
only elements from the signature associated with the tag are
exported.
In all cases, the export signature for the signed compound unit
includes a copy of each element from the effective signature.
The (unitunit-path) form exports the referenced
unit as an embedded unit. The export signature for the signed
compound unit includes a sub-signature corresponding to the effective
signature from unit-path. The name of the sub-signature in the
compound unit's export signature depends on unit-path:
If unit-path refers to a tagged import or a sub-unit,
then the tag is used for the sub-signature name.
Otherwise, the referenced sub-unit was an embedded unit, and
the original name for the associated sub-signature is re-used for
the export signature's sub-signature.
The (unitunit-pathidentifier) form exports an
embedded unit like (unitunit-path) form, but
identifier is used for the name of the sub-signature in the
compound unit's export signature.
The collection of names exported by a compound unit must form a legal
signature. This means that all exported names must be distinct.
Run-time checks insure that all link clause exprs evaluate
to a signed unit, and that all linkages match according to the
specified signatures:
If an expr evaluates to anything other than a signed
unit, the exn:unit exception is raised.
If the export signature for a signed unit does not satisfy the
signature specified with its tag, the exn:unit:signature exception is raised.
If the number of units specified in a linkage does not match the
number imported by a linking unit, the exn:unit exception is raised.
If the (effective) signature of a provided unit does not match
the corresponding import signature, then the exn:unit exception is raised.
Signed units are invoked using the invoke-unit/sig form:
(invoke-unit/sigexprinvoke-linkage···)
invoke-linkage is one of
signature
(identifier:signature)
If the invoked unit requires no imports, the invoke-unit/sig
form is used in the same way as invoke-unit. Otherwise, the
invoke-linkage signatures must match the import signatures of
the signed unit to be invoked. If the signatures match, then
variables in the environment of the invoke-unit/sig
expression are used for immediate linking; the variables used for
linking are the ones with names corresponding to the flattened
signatures. The signature flattening algorithm is specified in
section 35.2; when the (identifier:signature) form
is used, identifier: is prefixed onto each variable name
in the flattened signature and the prefixed name is used.
This form is the signed-unit version of
define-values/invoke-unit. The names defined by the
expansion of define-values/invoke-unit/sig are determined by
flattening the signature specified before unit-expr, then
adding the prefix (if any). See section 35.2 for more
information about signature flattening.
Each invoke-linkage is either signature or
(identifier:signature), as in invoke-unit/sig.
The procedure unit/sig->unit extracts and returns the
primitive unit from a signed unit.
The names exported by the primitive unit correspond to the
flattened export signature of the signed unit; see section 35.2
for the flattening algorithm.
The number of import variables for the primitive unit matches the
total number of variables in the flattened forms of the signed
unit's import signatures. The order of import variables is as follows:
All of the variables for a single import signature are grouped
together, and the relative order of these groups follows the order of
the import signatures.
Within an import signature:
variable names are ordered according to string<?;
all names from sub-signatures follow the variable names;
names from a single sub-signature are grouped together and
ordered within the sub-signature group following this algorithm
recursively; and
the sub-signatures are ordered among themselves using
string<? on the sub-signature names.
The unit->unit/sig syntactic form wraps a primitive unit
with import and export signatures:
(unit->unit/sigexpr (signature···) signature)
The last signature is used for the export signature and the
other signatures specify the import signatures. If expr
does not evaluate to a unit or the unit does not match the signature,
no error is reported until the primitive linker discovers the
problem.
The unit/sig, compound-unit/sig, and invoke-unit/sig
forms expand into expressions using the unit,
compound-unit, and invoke-unit forms, respectively.
A signed unit value is represented by a signed-unit structure
with the following fields:
unit -- the primitive unit implementing the signed unit's content
imports -- the import signatures, represented as a list
of pairs, where each pair consists of
a tag symbol, used for error reporting; and
an ``exploded signature''; an exploded signature is a vector of
signature elements, where each element is either
a symbol, representing a variable in the signature; or
a pair consisting of a symbol and an exploded signature,
representing a name sub-signature.
exports -- the export signature, represented as an
exploded signature
To perform the signature checking needed by compound-unit/sig,
MzScheme provides two procedures:
(verify-signature-matchwhere exact? dest-context dest-sig
src-context src-sig) raises an exception unless the exploded
signatures dest-sig and src-sig match. If exact? is
#f, then src-sig need only satisfy dest-sig,
otherwise the signatures must match exactly. The where symbol
and dest-context and src-context strings are used for
generating an error message string: where is used as the name
of the signaling procedure and dest-context and
src-context are used as the respective signature names.
If the match succeeds, void is returned. If the match fails, the
exn:unit exception is raised for one of the following reasons:
The signatures fail to match because src-sig is missing
an element.
The signatures fail to match because src-sig contains
an extra element.
The signatures fail to match because src-dest and
src-sig contain the same element name but for different
element types.
(verify-linkage-signature-matchwhere tags units
export-sigs linking-sigs) performs all of the run-time signature
checking required by a compound-unit/sig or
invoke-unit/sig expression. The where symbol is used for
error reporting. The tags argument is a list of tag symbols,
and the units argument is the corresponding list of candidate
signed unit values. (The procedure will check whether these values
are actually signed unit values.)
The export-sigs list contains one exploded signature for each
tag; these correspond to the tag signatures provided in the original
compound-unit/sig expression. The linking-sigs list
contains a list of named exploded signatures for each tag (where a
``named signature'' is a pair consisting of a name symbol and an
exploded signature); every tag's list corresponds to the signatures
that were specified or inferred for the tag's linkage specification
in the original compound-unit/sig expression. The names on the
linking signatures are used for error messages.
If all linking checks succeed, void is returned. If any check
fails, the exn:unit exception is raised for one of the following reasons:
A value in the units list is not a signed unit.
The number of import signatures associated with a unit does
not agree with the number of linking signatures specified by the
corresponding list in linking-sigs.
A linking signature does not exactly match the signature
expected by an importing unit.
(signature->symbolsname) SYNTAX
Expands to the ``exploded'' version (see section 35.11) of the
signature bound to name (where name is an
unevaluated identifier).
13 Of course, a signature can be specified as an immediate signature.
14 Element names are compared using the printed form
of the name. This is different from any other syntactic form, where
variable names are compared as symbols. This distinction is relevant
only when source code is generated within Scheme rather than read
from a text source.