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 (tag
identifier sig-expr) sig-expr is one of sig-identifier (prefix identifier sig-expr) (rename sig-expr (identifier identifier) ···) (only
sig-expr identifier ···) (except
sig-expr identifier ···) init-depends-decl is one of epsilon (init-depend
tagged-sig-identifier ···) tagged-sig-identifier is one of sig-identifier (tag
identifier sig-identifier)
The result of a unit
form is a unit value that encapsulates
its unit-body-expr-or-defn
s. Expressions in the unit
body can refer to identifiers bound by the sig-expr
s 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
identifier
in a signature declaration means that a unit implementing the signature must supply a variable definition for theidentifier
. That is,identifier
is available for use in units importing the signature, andidentifier
must be defined by units exporting the signature.Each
define-syntaxes
form 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'sexpr
refer to other identifiers in the signature first, or the context of thedefine-signature
form if the signature does not include the identifier.Each
define-values
form in a signature declaration introduces code that effectively prefixes every unit that imports the signature. Free variables in the definition'sexpr
are 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 theidentifier
prefix to satisfy the exports required bysig-expr
.(rename sig-expr (identifer identifier) ···)
as an import binds the same assig-expr
, except that the firstidentifier
is used for the binding instead of the secondidentifier
(wheresig-expr
by itself must imply a binding for the secondidentifier
). As an export, this form causes a definition for the firstidentifier
to satisfy the export named by the secondidentifier
insig-expr
.(
as an import binds the same asonly
sig-expr identifier ···)sig-expr
, but restricted to just the listedidentifier
s (wheresig-expr
by itself must imply a binding for eachidentifier
). This form is not allowed for an export.(
as an import binds the same asexcept
sig-expr identifier ···)sig-expr
, but excluding all listedidentifier
s (wheresig-expr
by 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 extends
sig-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
)) (display
x) (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
)) (display
x) (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
(cons
name info) table)))) (define lookup (lambda (name default) (let ([data (assoc name table)]) (if data (cdr
data) (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-spec
s 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 display
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 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^ [display
show-message])) (export (prefix x: database^))) (define-values/invoke-unit database@ (import (rename interface^ [display
show-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 (tag
b (prefix y: database^)) (tag
a (prefix x: database^))) (export (prefix m: database^)))
The
annotations indicate which given import is meant to
supply each expected imoprt of tag
merger@
. 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-expr
s in the link
clause determine the units
to be linked in creating the compound unit. The unit-expr
s 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-depend
exn: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-identifier
s in the
export
clause by link-identifier
s, 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 display
show-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-expr
s. 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
:
(struct
identifier (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 identifier
identifier
(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-expr
s. 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.