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

The unit form creates a unit:

(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-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:

When a define-signature form includes a extends 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.

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:

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 (tag identifier sig-expr). 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.

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 extends. 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 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 (init-depend tagged-sig-identifier ···) 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 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-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 display procedure. Invocation of the linked unit then creates an empty database, and it internally defines 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 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 tag annotations indicate which given import is meant to supply each expected imoprt of 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-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 init-depend declarations of the actual units, then the 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-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 display from the current definition context as 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-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:

(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:identifier to be omitted, -selectors causes all identifier-field-identifiers to be omitted, -setters causes all set-identifier-field-identifier!s to be omitted, and -construct causes make-identifier to be omitted. These omissions are reflected in the static information bound to 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.

(unit? v)      PROCEDURE

Returns #t if v is a unit, #f otherwise.

(provide-signature-elements sig-expr ···)      SYNTAX

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.