unit.ss: Units
To load: (require (lib "unit.ss"))
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 pluggable 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.
55.1 Creating Units
(unit (import tagged-sig-expr ···) (export tagged-sig-expr ···) init-depends-decl unit-body-expr-or-defn ···) tagged-sig-expr is one of sig-expr (tagidentifier sig-expr) sig-expr is one of sig-identifier (prefix identifier sig-expr) (rename sig-expr (identifier identifier) ···) (onlysig-expr identifier ···) (exceptsig-expr identifier ···) init-depends-decl is one of epsilon (init-dependtagged-sig-identifier ···) tagged-sig-identifier is one of sig-identifier (tagidentifier sig-identifier)
The result of a unit form is a unit value that encapsulates
its unit-body-expr-or-defns. Expressions in the unit
body can refer to identifiers bound by the sig-exprs of the
import clause, and the body must include one definition for
each identifier of a sig-expr in the export clause.
An identifer that is exported cannot be set!ed in either
the defining unit or in importing units, although the implicit
assignment to initialize the variable may be visible as a mutation.
Each import or export sig-expr ultimately refers to a
sig-identifier, which is an identifier that is bound to a signature
by define-signature:
(define-signature identifier extension-decl
(sig-spec ···))
extension-decl is one of
epsilon
extends sig-identifier
sig-spec is one of
identifier
(define-syntaxes (identifier ···) expr)
(define-values (value-id ···) expr)
(open sig-expr)
(sig-form-identifier . datum)
The define-signature form binds a signature to specify a group
of bindings for import or export:
Each
identifierin a signature declaration means that a unit implementing the signature must supply a variable definition for theidentifier. That is,identifieris available for use in units importing the signature, andidentifiermust be defined by units exporting the signature.Each
define-syntaxesform in a signature declaration introduces a macro to that is available for use in any unit that imports the signature. Free variables in the definition'sexprrefer to other identifiers in the signature first, or the context of thedefine-signatureform if the signature does not include the identifier.Each
define-valuesform in a signature declaration introduces code that effectively prefixes every unit that imports the signature. Free variables in the definition'sexprare treated the same as fordefine-syntaxes.Each
(open sig-expr)adds to the signature everything specified bysig-expr.Each
(sig-form-identifier . datum)extends the signature in a way that is defined bysig-form-identifier, which must be bound bydefine-signature-form(see section 55.7). One such binding is forstruct(see section 55.7).
When a define-signature form includes a clause,
then the define signature automatically includes everything in the
extended signature. Furthermore, any implementation of the new signature
can be used as an implementation of the extended signature.extends
In a specific import or export position, the set of identifiers bound
or required by a particular sig-identifier can be adjusted in a
few ways:
(prefix identifier sig-expr)as an import binds the same assig-expr, except that each binding is prefixed withidentifier. As an export, this form causes definitions using theidentifierprefix to satisfy the exports required bysig-expr.(rename sig-expr (identifer identifier) ···)as an import binds the same assig-expr, except that the firstidentifieris used for the binding instead of the secondidentifier(wheresig-exprby itself must imply a binding for the secondidentifier). As an export, this form causes a definition for the firstidentifierto satisfy the export named by the secondidentifierinsig-expr.(as an import binds the same asonlysig-expr identifier ···)sig-expr, but restricted to just the listedidentifiers (wheresig-exprby itself must imply a binding for eachidentifier). This form is not allowed for an export.(as an import binds the same asexceptsig-expr identifier ···)sig-expr, but excluding all listedidentifiers (wheresig-exprby itself must imply a binding for eachidentifier). This form is not allowed for an export.
As suggested by the grammar, these adjustments to a signature can be nested arbitrarily.
A unit's declared imports are matched with actual supplied imports by signature. That is, the order in which imports are suppplied to a unit when linking is irrelevant; all that matters is the signature implemented by each supplied import. One actual import must be provided for each declared import. Similarly, when a unit implements multiple signatures, the order of the export signatures does not matter.
To support multiple imports or exports for the same signature, an
import or export can be tagged using the form (. When an import declaration of a unit is
tagged, then one actual import must be given the same tag (with the
same signature) when the unit is linked. Similarly, when an export
declaration is tagged for a unit, then references to that particular
export must explicitly use the tag.tag
identifier sig-expr)
A unit is prohibited syntactically from importing two signatures that
are not distinct, unless they have different tags; two signatures are
distinct only if when they share no ancestor through
. The same syntactic constraint applies to exported
signatures. In addition, a unit is prohibited syntactically from
importing the same identifier twice (after renaming and other
transformations on a extendssig-expr), exporting the same identifier
twice (again, after renaming), or exporting an identifier that is
imported.
When units are linked, the bodies of the linked units are
executed in an order that is specified at the linking site. An
optional (
declaration constrains the allowed orders of linking by specifying
that the current unit must be initialized after the unit that supplies
the corresponding import. Each init-depend tagged-sig-identifier ···)tagged-sig-identifier in an
init-depend declaration must have a corresponding import in the
import clause.
Examples
The unit defined below imports and exports no variables. Each time it is invoked, it prints and returns the current time in seconds:15
(define f1@ (unit (import) (export) (define x (current-seconds)) (displayx) (newline) x))
The unit defined below is similar, except that it exports the variable
x instead of returning the value:
(define-signature f2^ (x)) (define f2@ (unit (import) (export f2^) (define x (current-seconds)) (displayx) (newline)))
The following units define two parts of an interactive phone book:
(define-signature interface^ (show-message)) (define-signature database^ (insert lookup)) (define-signature gui^ (make-window make-button)) (define database@ (unit (import interface^) (export database^) (define table (list)) (define insert (lambda (name info) (set! table (cons(consname info) table)))) (define lookup (lambda (name default) (let ([data (assoc name table)]) (if data (cdrdata) (or default (show-message "info not found")))))) insert)) (define interface@ (unit (import database^ gui^) (export interface^) (define show-message (lambda (msg) ...)) (define main-window ...)))
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 a platform-specific graphics
toolbox.
The following merger@ unit import two units that implement
database^, and it also implements database^
itself. The unit implements lookup by checking both of
the imported databases,
and it implements insert by inserting into both of the imported
databases. Since the merger@ unit must import two
instances of database^, it gives each import a tag -- a
or b -- that must be used at link time to specify the
imports. Specifically, the link tagged with a will designate
the database implementation to be consulted first by the merged
database's lookup. Finally, the unit uses a prefix
form to distinguish each set of imported names internally.
(define merger@ (unit (import (tag a (prefix a: database^)) (tag b (prefix b: database^))) (export database^) (define (insert name info) (a:insert name info) (b:insert name info)) (define (lookup name default) (or (a:lookup name #f) (b:lookup name default)))))
55.2 Invoking Units
A unit is invoked using the invoke-unit form:
(invoke-unit unit-expr) (invoke-unit unit-expr (import tagged-sig-spec ···))
The value of unit-expr must be a unit. For each of the unit's
imports, the invoke-unit expression must contain
a tagged-sig-spec in the import clause.
If the unit has no imports, the import clause can be omitted.
When no tagged-sig-specs are provided, unit-expr must
produce a unit that expect no imports. To invoke the unit, all
bindings are first initialized to the undefined value. Next, the
unit's body definitions and expressions are evaluated in
order; in the case of a definition, evaluation sets the value of the
corresponding variable(s). Finally, the result of the last expression
in the unit is the result of the invoke-unit expression.
Each supplied tagged-sig-spec takes bindings from the
surrounding context and turns them into imports for the invoked unit.
The unit need not declare an imports for evey provided
tagged-sig-spec, but one tagged-sig-spec must be provided
for each declared import of the unit. For each variable identifier in
each provided tagged-sig-spec, the value of the identifier's
binding in the surrounding context is used for the corresponding
import in the invoked unit.
The define-values/invoke-unit form is like
invoke-unit, but the values of the unit's exports are copied
to new bindings.
(define-values/invoke-unit unit-expr (import tagged-sig-spec ···) (export tagged-sig-spec ···))
The unit produced by unit-expr is linked and invoked as for
invoke-unit. In addition, the export clause is treated
as a kind of import into the local definition context. That is, for
every binding that would be available in a unit that used the
export clauses's tagged-sig-spec as an import, a definition
is generated for the context of the define-values/invoke-unit
form.
Examples
These examples use the definitions from the earlier unit examples in section 55.1.
The f1@ unit can be invoked with no imports:
(invoke-unit f1@) ; => displays and returns the current time
The database@ unit also can be invoked directly:
(invoke-unit database@
(import (rename interface^ [display show-message])))
This expression links the imported variable show-message in
database@ to the standard Scheme procedure.
Invocation of the linked unit then creates an empty database, and it
internally defines the procedures displayinsert
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 lookup
procedure is not accessible.
Since the lookup procedure is not accessible, simply
invoking database@ is useless. In contrast,
(define-values/invoke-unit database@
(import (rename interface^ [display show-message]))
(export database^))
invokes database@ in the same way, but also defines
insert and lookup, binding them to the exports of the
invoked unit.
To create two separate instances of the database in the current
binding context, we can include a prefix in export clause of
of define-values/invoke-unit to create separate sets of bindings.
The following pair of definitions bind x:insert, x:lookup,
y:insert, and y:lookup:
(define-values/invoke-unit database@ (import (rename interface^ [displayshow-message])) (export (prefix x: database^))) (define-values/invoke-unit database@ (import (rename interface^ [displayshow-message])) (export (prefix y: database^)))
These sets of database^ bindings can be supplied as imports
for invoking the merger@ unit:
(define-values/invoke-unit merger@ (import (tagb (prefix y: database^)) (taga (prefix x: database^))) (export (prefix m: database^)))
The annotations indicate which given import is meant to
supply each expected imoprt of tagmerger@. The order within
import does not matter, and to illustrate this point, the
imports they are supplied above in opposite order to the declaration
order. The prefix annotations construct names that are drawn
from the surrounding context. That is, y:insert and
y:lookup are taken from the surrounding context and used for
the b-tagged import, and x:insert and
x:lookup are used for the a-tagged import. Finally,
the defined names are prefixed with m:.
55.3 Linking Units and Creating Compound Units
The compound-unit form links several units into one new
compound unit without immediately invoking any of the linked units.
(compound-unit
(import link-binding ···)
(export tagged-link-identifier ···)
(link linkage-decl ···))
link-binding is
(link-identifier : tagged-sig-identifier)
tagged-link-identifier is one of
(tag identifier link-identifier)
link-identifier
linkage-decl is
((link-binding ···) unit-expr tagged-link-identifier)
The unit-exprs in the link clause determine the units
to be linked in creating the compound unit. The unit-exprs are
evaluated when the compound-unit form is evaluated.
The import clause determines the imports of the compound
unit. Outside the compound unit, these imports behave as for a plain
unit; inside the compound unit, they are propagated to some of the
linked units. The export clause determines the exports of
the compound unit. Again, outside the compound unit, these exports
are trested the same as for a plain unit; inside the compound unit,
they are drawn from the exports of the linked units. Finally, the
left-hand and right-hand parts of each declaration in the
link clause specify how the compound unit's imports and
exports are propagated to the linked units.
Individual elements of an imported or exported signature are not
available within the compound unit. Instead, imports and exports are
connected at the level of whole signatures. Each specific import
or export (i.e., an instance of some signature, possibly tagged) is given a
link-identifier name. Specifically, a link-identifier is
bound by the import clause or the left-hand part of an
declaration in the link clause. A bound
link-identifier is referenced in the right-hand part of a
declaration in the link clause or by the export
clause.
The left-hand side of a link declaration gives names to each
expected export of the unit produced by the corresponding
unit-expr. The actual unit may export additional signatures, and
it may export an extension of a specific signature instead of just
the specified one. If the unit does not export one of the specified
signatures (with the specified tag, if any), the exn:fail:contract exception is raised
when the compound-unit form is evaluated.
The right-hand side of a link declaration specifies the
imports to be supplied to the unit produced by the corresponding
unit-expr. The actual unit may import fewer signatures, and
it may import a signature that is extended by the specified one.
If the unit imports a signature (with a particular tag) that is not
included in the supplied imports, the exn:fail:contract exception is raised
when the compound-unit form is evaluated. Each link-identifier
supplied as an import must be bound either in the import
clause or in some declaration within the link clause.
The order of declarations in the link clause determines the
order of invocation of the linked units. When the compound unit is
invoked, the unit produced by the first unit-expr is invoked
first, then the second, and so on. If the order specified in the
link clause is inconsistent with
declarations of the actual units, then the
init-dependexn:fail:contract exception is raised when the compound-unit form is
evaluated.
Examples
These examples use the definitions from the earlier unit examples in section 55.1.
The following example shows how the database@ and
interface@ units are linked together. The compound
unit still must be linked with a GUI unit before the interactive
phone book works. In addition, the database@ exports
are propagated, in case the unit is useful in a larger program
that manipulates the database directly.
(define phonebook@ (compound-unit (import (GUI : gui^)) (export DATABASE) (link [((DATABASE : database^)) database@ INTERFACE] [((INTERFACE : interface^)) interface@ DATABASE GUI])))
If gui@ is bound to a unit that exports gui^ (at
least) and imports nothing, then a complete phonebook program can be
linked as follows:
(define program@ (compound-unit (import) (export) (link [((GUI : gui^)) gui@] [((PHONEBOOK : database^)) phonebook@ GUI])))
This phone book program is executed with (invoke-unit
program@). If (invoke-unit program@) is evaluated a second
time, then a new, independent database and window are created.
The following merge-databases function takes two database
units and links them into a single database unit:
(define (merge-databases a@ b@) (compound-unit (import (INTERFACE : interface^)) (export DBM) (link [((DBA : database^)) a@ INTERFACE] [((DBB : database^)) b@ INTERFACE] [((DBM : database^)) merger@ (tag a DBA) (tag b DBB)])))
The link clause for the merger@ unit matches each of
the DBA and DBB units with a tag a or
b, since merger@ requires a tag for each of its
imports. The compound unit produced by merge-database
re-exports the insert and lookup functions of the
merger@ unit, since DBM is used in the
export clause.
55.4 Inferred Linking
The examples of the previous section include considerable information in
the link clause that seems redundant to a human
reader. For example, when linking database@, we specify again
that it must export a database^ signature, but the
fact of this export is readily available from the preceding definition
of database@. Of course, since units are first-class values
and Scheme is dynamically typed, the exports of the unit produced by a
unit-expr are not always so readily available. Nevertheless,
units are frequently defined and used in an environment where the
bindings can be made apparent.
The define-unit helps avoid redundancy by combining
binding the defined identifier to both a unit value and static
information about the unit's imports and exports:
(define-unit unit-identifier (import tagged-sig-expr ···) (export tagged-sig-expr ···) init-depends-decl unit-body-expr-or-defn ···)
Evaluating a reference to an unit-identifier bound by define-unit
produces a unit, just like evaluating an identifier bound by
(define identifier (unit ...)). In addition, however,
unit-identifier can be used in compound-unit/infer:
(compound-unit/infer
(import tagged-infer-link-import ···)
(export tagged-infer-link-export ···)
(link infer-linkage-decl ···))
tagged-infer-link-import is
tagged-sig-identifier
(link-identifier : tagged-sig-identifier)
tagged-infer-link-export is one of
(tag identifier infer-link-export)
infer-link-export
infer-link-export is one of
link-identifier
sig-identifier
infer-linkage-decl is one of
((link-binding ···) unit-identifier tagged-link-identifier)
unit-identifier
Syntactically, the difference between compound-unit and
compound-unit/infer is that the unit-expr for a linked
unit is replaced with a unit-identifier, where a
unit-identifier is bound by define-unit (or one of the
other unit-binding forms that we introduce later in this
section). Furthermore, an import can name just a sig-identifier
without locally binding a link-identifier, and an export can be
based on a sig-identifier instead of a link-identifier,
and a declaration in the link clause can be simply a
unit-identifier with no specified exports or imports.
The compound-unit/infer form expands to
compound-unit by adding sig-identifiers as needed to
the import clause, by replacing sig-identifiers in the
export clause by link-identifiers, and by completing
the declarations of the link clause. This completion is based
on static information associated with each
unit-identifier. Links and exports can be inferred when all
signatures exported by the linked units are distinct from each other
and from all imported signatures, and when all imported signatures are
distinct. Two signatures are distinct only if when they
share no ancestor through .extends
The long form of a link declaration can be used to resolve
ambiguity by giving names to some of a unit's exports and supplying
specific bindings for some of a unit's imports. The long form need not
name all of a unit's exports or supply all of a unit's imports if the
remaining parts can be inferred.
Like compound-unit, the compound-unit/infer form
produces a (compound) unit without statically binding information
about the result unit's imports and exports. That is,
compound-unit/infer consumes static information, but it does
not generate it. Two additional forms,
define-compound-unit and
define-compound-unit/infer, generate static information
(where the former does not consume static information).
(define-compound-unit identifier (import link-binding ···) (export tagged-link-identifier ···) (link linkage-decl ···)) (define-compound-unit/infer identifier (import link-binding ···) (export tagged-infer-link-export ···) (link infer-linkage-decl ···))
An existing unit value can be associated with static information
via define-unit-binding:
(define-unit-binding unit-identifier unit-expr (import tagged-sig-expr ···1) (export tagged-sig-expr ···1) init-depends-decl)
This form is like define-unit, except that the unit
implementation is determined from an existing unit produced by
unit-expr. The imports and exports of the unit produced by
unit-expr must be consistent with the declared imports and
exports, otherwise the exn:fail:contract exception is raised when the
define-unit-binding form is evaluated.
Like compound-unit/infer, the invoke-unit/infer
and define-values/invoke-unit/infer use static
information to infer which imports must be assembled from the current
context, and (in the case of the latter) what exports should be bound
by the definition:
(invoke-unit/infer unit-identifer) (define-values/invoke-unit/infer unit-identifer)
Examples
To take advantage of link inference for the phone book example from
previous sections, we must change the bindings of units to use
define-unit instead of define:
(define-unit database@
(import interface^)
(export database^)
(define table (list))
(define insert ...)
(define lookup ...)
insert)
(define-unit interface@
(import database^ gui^)
(export interface^)
(define show-message ...)
(define main-window ...))
To invoke database@ directly, we can use the
invoke-unit form as before. Alternately, we can now use
invoke-unit/infer in a context that binds
show-message:
(let ([show-message display])
(invoke-unit/infer database@))
To gain access to the exports of database@, we can use
define-values/invoke-unit/infer:
(define show-message display)
(define-values/invoke-unit/infer database@)
... insert ...
... lookup ...
The database@ and interface@ units can be
linked succinctly by relaying on inference:
(define-compound-unit/infer phonebook@ (import gui^) (export database^) (link database@ interface@))
In this case, the import clause simply names gui^
without a link-identifier, since all uses of the imported
interface can be inferred. Similarly, the export simply names
database^, since the link clause includes only one
candidate implementation of the database^ interface. Finally,
the imports for database@ and interface@ are
unambiguous, so they can be inferred.
Even after we change merger@ to use define-unit, the
links inside merge-databases cannot be fully inferred:
(define (merge-databases a@ b@) (compound-unit/infer (import interface^) (export database^) (link a@ b@ merger@))) ; does not work
There are three problems with inference. First, the a@ and
b@ units are supplied as first-class values to a procedure,
so no static information is available about their imports and exports.
Second, the merger@ unit imports two instances of the
database^ signature, and each could be supplied by
a@, b@, or even merger@ itself. Finally,
the database^ export of the overall compound-unit
could be supplied by any of the three database units.
The first problem can be solved by using define-unit-binding
to locally declare imports and exports for a@ and b@.
The second problem can be solved by giving linked instances of
a@ and b@ the link names DBA and DBB.
The third problem can be solved by giving the merger@ instance
a name and using it in export:
(define (merge-databases some-a@ some-b@) (define-unit-binding a@ some-a@ (import interface^) (export database^)) (define-unit-binding b@ some-b@ (import interface^) (export database^)) (compound-unit/infer (import interface^) (export DBM) (link [((DBA : database^) a@] [((DBB : database^) b@] [((DBM : database^) merger@ (tag a DBA) (tag B DBB))])))
Although the interface^ links to a@ and b@
are inferred, this definition of merge-database turns out to
be more verbose than the one that avoids inference altogether.
55.5 Generating A Unit from Context
The unit-from-context form creates a unit that
implements an interface using bindings in the enclosing context.
(unit-from-context tagged-sig-expr)
The generated unit is essentially the same as
(unit (import) (export tagged-sig-expr) (define identifier expr) ···)
for each identifier that must be defined to satisfy the exports,
and each corresponding expr produces the value of
identifier in the context of the unit-from-context
expression. (The unit cannot be written as above, however, since each
identifier definition within the unit shadows the binding outside
the unit form.)
Like define-unit, define-unit-from-context binds
static information to be used later with inference.
(define-unit-from-context identifier tagged-sig-expr)
Examples
The following declaration creates a unit that implements
interface^ by using from the current
definition context as displayshow-message.
(define-unit-from-context display-interface@
(rename interface^
[display show-message]))
The resulting unit can be linked with database@ to
define simple-phonebook@:
(define-compound-unit/infer simple-phonebook@ (import gui^) (export database^) (link database@ display-interface@))
55.6 Structural Matching
Units are linked by name. That is, unit imports are matched with unit exports only when they name the same interface. Sometimes, a unit imports or exports a set of identifier that match a particular signature, but the unit declares the import or export using a different signature. In such cases, the unit can be wrapped with new imports and exports that are matched by structure to the unit's existing imports and exports; that is, only the contents of the signatures matter, not the names, when matching the unit's original imports exports with the new imports and exports.
The unit/new-import-export form converts a unit's
import and export signatures structrurally:
(unit/new-import-export (import tagged-sig-expr ···) (export tagged-sig-expr ···) init-depends-decl ((tagged-sig-expr ···) unit-expr tagged-sig-expr))
The result is a unit that whose implementation is unit-expr, but
whose imports, exports, and initialization dependencies are
as in the unit/new-import-export form (instead of as in the unit
produced by unit-expr).
The final clause of the unit/new-import-export form
determines the connection between the old and new imports and
exports. The connection is similar to the way that
compound-unit propagates imports and exports; the difference
is that the connection between import and the right-hand
side of the link clause is based on the names of elements in
signatures, rather than the names of the signatures. That is, a
tagged-sig-spec on the right-hand side of the link clause need
not apppear as a tagged-sig-expr in the import clause,
but each of the bindings implied by the linking tagged-sig-spec
must be implied by some tagged-sig-spec in the import
clause. Similarly, each of the bindings implied by an export
tagged-sig-spec must be implied by some left-hand-side
tagged-sig-spec in the linking clause.
The define-unit/new-import-export is similar, but it binds
import and export information statically to a unit-identifier:
(define-unit/new-import-export unit-identifier (import tagged-sig-expr ···) (export tagged-sig-expr ···) init-depends-decl ((tagged-sig-expr ···) unit-expr tagged-sig-expr))
Examples
Suppose that we have an existing implementation of dictionaries that we would like to use as a database:
(define-signature dictionary^ (lookup insert get-count)) (define-unit dictionary@ (import) (export dictionary^) (define (lookup name default) ...) (define (insert name val) ...) (define get-count ...))
The dictionary unit cannot be used directly as an
implementation of database^, even though its export names
happen to match, because it does not declare database^
as an export.
We can create a new unit that behaves exactly like dictionary@,
except that it implements the database^ signature:
(define-signature dictionary^ (lookup insert get-count)) (define-unit/new-import-export dictionary-dc@ (import) (export database^) ((dictionary^) dictionary@))
55.7 Extending the Syntax of Signatures
The syntax of the define-signature form can be macro-extended
using define-signature-form:
(define-signature-form sig-form-identifier expr) (define-signature-form (sig-form-identifier identifier) body-expr ···1)
In the first form, the result of expr must be a transformer
procedure. In the second form, sig-form-identifier is bound to
a transformer procedure whose argument is identifier and whose
body is the body-exprs. The result of the transformer must be a
list of syntax objects, which are substituted for a use of
sig-form-identifier in a define-signature
expansion. (The result is a list so that the transformer can produce
multiple declarations; define-signature has no splicing
begin form.)
An example signature macro is struct, which expands in a
way analogous to define-struct:
(structidentifier (field-identifier ···) omit-decl ···) omit-decl is one of-type-selectors-setters-constructor
The expansion of a struct signature form includes all of the
identifiers that would be bound by (define-struct identifier
(field-identifier ···)), except that a omit-decl can cause
some of the bindings to be omitted. Specifically -type causes
struct: to be omitted, identifier-selectors
causes all s to be
omitted, identifier-field-identifier-setters causes all
set-s to be omitted,
and identifier-field-identifier!-construct causes make- to be
omitted. These omissions are reflected in the static information
bound to identifieridentifier (which is used by, for example, pattern
matchers).
55.8 Unit Utilities
See also the unit-exptime.ss library (see section 56) for procedures useful to macro transformers.
Returns #t if v is a unit, #f otherwise.
( SYNTAX
provide-signature-elements sig-expr ···)
Expands to a provide of all identifiers implied by the
sig-exprs. See section 55.1 for the grammar of
sig-expr.
15 The ``@'' in the variable name ``f1@''
indicates (by convention) that its value is a unit.