#lang scribble/doc @(require (except-in "mz.ss" link) (for-label scheme/unit-exptime)) @(define-syntax defkeywords (syntax-rules (*) [(_ [* (form ...) as see]) (defform* [form ...] "Allowed only in a " (scheme as) "; see " (scheme see) ".")] [(_ [* (form ...) see-eg]) (defform* [form ...] "Allowed only in certain forms; see, for example, " (scheme see-eg) ".")] [(_ [form as see]) (defkeywords [* (form) as see])] [(_ [form see-eg]) (defkeywords [* (form) see-eg])] [(_ f ...) (begin (defkeywords f) ...)])) @title[#:tag "mzlib:unit" #:style 'toc]{Units} @deftech{Units} organize a program into separately compilable and reusable components. The imports and exports of a unit are grouped into a @deftech{signature}, which can include ``static'' information (such as macros) in addition to placeholders for run-time values. Units with suitably matching signatures can be @deftech{linked} together to form a larger unit, and a unit with no imports can be @deftech{invoked} to execute its body. @note-lib[scheme/unit #:use-sources (mzlib/unit)]{ The @schememodname[scheme/unit] module name can be used as a language name with @schemefont{#lang}; see @secref["single-unit"].} @local-table-of-contents[] @; ------------------------------------------------------------------------ @section[#:tag "creatingunits"]{Creating Units} @defform/subs[ #:literals (import export prefix rename only except tag init-depend tag) (unit (import tagged-sig-spec ...) (export tagged-sig-spec ...) init-depends-decl unit-body-expr-or-defn ...) ([tagged-sig-spec sig-spec (tag id sig-spec)] [sig-spec sig-id (prefix id sig-spec) (rename sig-spec (id id) ...) (only sig-spec id ...) (except sig-spec id ...)] [init-depends-decl code:blank (init-depend tagged-sig-id ...)] [tagged-sig-id sig-id (tag id sig-id)])]{ Produces a unit that encapsulates its @scheme[unit-body-expr-or-defn]s. Expressions in the @scheme[unit] body can refer to identifiers bound by the @scheme[sig-spec]s of the @scheme[import] clause, and the body must include one definition for each identifier of a @scheme[sig-spec] in the @scheme[export] clause. An identifier that is exported cannot be @scheme[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 @scheme[sig-spec] ultimately refers to a @scheme[sig-id], which is an identifier that is bound to a signature by @scheme[define-signature]. In a specific import or export position, the set of identifiers bound or required by a particular @scheme[sig-id] can be adjusted in a few ways: @itemize{ @item{@scheme[(prefix id sig-spec)] as an import binds the same as @scheme[sig-spec], except that each binding is prefixed with @scheme[id]. As an export, this form causes definitions using the @scheme[id] prefix to satisfy the exports required by @scheme[sig-spec].} @item{@scheme[(rename sig-spec (id id) ...)] as an import binds the same as @scheme[sig-spec], except that the first @scheme[id] is used for the binding instead of the second @scheme[id] (where @scheme[sig-spec] by itself must imply a binding for the second @scheme[id]). As an export, this form causes a definition for the first @scheme[id] to satisfy the export named by the second @scheme[id] in @scheme[sig-spec].} @item{@scheme[(only sig-spec id ...)] as an import binds the same as @scheme[sig-spec], but restricted to just the listed @scheme[id]s (where @scheme[sig-spec] by itself must imply a binding for each @scheme[id]). This form is not allowed for an export.} @item{@scheme[(except sig-spec id ...)] as an import binds the same as @scheme[sig-spec], but excluding all listed @scheme[id]s (where @scheme[sig-spec] by itself must imply a binding for each @scheme[id]). 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 @scheme[(tag id sig-spec)]. 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 @defterm{distinct} only if when they share no ancestor through @scheme[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 @scheme[sig-spec]), 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 @scheme[(init-depend tagged-sig-id ...)] 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 @scheme[tagged-sig-id] in an @scheme[init-depend] declaration must have a corresponding import in the @scheme[import] clause.} @defform/subs[ #:literals (define-syntaxes define-values open extends) (define-signature id extension-decl (sig-elem ...)) ([extension-decl code:blank (code:line extends sig-id)] [sig-elem id (define-syntaxes (id ...) expr) (define-values (value-id ...) expr) (open sig-spec) (sig-form-id . datum)])]{ Binds an identifier to a signature that specifies a group of bindings for import or export: @itemize{ @item{Each @scheme[id] in a signature declaration means that a unit implementing the signature must supply a variable definition for the @scheme[id]. That is, @scheme[id] is available for use in units importing the signature, and @scheme[id] must be defined by units exporting the signature.} @item{Each @scheme[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's @scheme[expr] refer to other identifiers in the signature first, or the context of the @scheme[define-signature] form if the signature does not include the identifier.} @item{Each @scheme[define-values] form in a signature declaration introduces code that effectively prefixes every unit that imports the signature. Free variables in the definition's @scheme[expr] are treated the same as for @scheme[define-syntaxes].} @item{Each @scheme[(open sig-spec)] adds to the signature everything specified by @scheme[sig-spec].} @item{Each @scheme[(sig-form-id . datum)] extends the signature in a way that is defined by @scheme[sig-form-id], which must be bound by @scheme[define-signature-form]. One such binding is for @scheme[struct].} } When a @scheme[define-signature] form includes a @scheme[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.} @defkeywords[[(open sig-spec) _sig-elem define-signature] [(only sig-spec id ...) _sig-spec unit] [(except sig-spec id ...) _sig-spec unit] [(rename sig-spec (id id) ...) _sig-spec unit] [(prefix id sig-spec) _sig-spec unit] [(import tagged-sig-spec ...) unit] [(export tagged-sig-spec ...) unit] [(link linkage-decl ...) compound-unit] [* [(tag id sig-spec) (tag id sig-id)] unit] [(init-depend tagged-sig-id ...) init-depend-decl unit]] @defidform[extends]{ This form is allowed only within @scheme[define-signature].} @; ------------------------------------------------------------------------ @section[#:tag "invokingunits"]{Invoking Units} @defform*[#:literals (import) [(invoke-unit unit-expr) (invoke-unit unit-expr (import tagged-sig-spec ...))]]{ Invokes the unit produced by @scheme[unit-expr]. For each of the unit's imports, the @scheme[invoke-unit] expression must contain a @scheme[tagged-sig-spec] in the @scheme[import] clause; see @scheme[unit] for the grammar of @scheme[tagged-sig-spec]. If the unit has no imports, the @scheme[import] clause can be omitted. When no @scheme[tagged-sig-spec]s are provided, @scheme[unit-expr] must produce a unit that expect no imports. To invoke the unit, all bindings are first initialized to the @|undefined-const| 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 @scheme[invoke-unit] expression. Each supplied @scheme[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 @scheme[tagged-sig-spec], but one @scheme[tagged-sig-spec] must be provided for each declared import of the unit. For each variable identifier in each provided @scheme[tagged-sig-spec], the value of the identifier's binding in the surrounding context is used for the corresponding import in the invoked unit.} @defform[ #:literals (import export) (define-values/invoke-unit unit-expr (import tagged-sig-spec ...) (export tagged-sig-spec ...))]{ Like @scheme[invoke-unit], but the values of the unit's exports are copied to new bindings. The unit produced by @scheme[unit-expr] is linked and invoked as for @scheme[invoke-unit]. In addition, the @scheme[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 @scheme[export] clauses's @scheme[tagged-sig-spec] as an import, a definition is generated for the context of the @scheme[define-values/invoke-unit] form.} @; ------------------------------------------------------------------------ @section[#:tag "compoundunits"]{Linking Units and Creating Compound Units} @defform/subs[ #:literals (: import export link tag) (compound-unit (import link-binding ...) (export tagged-link-id ...) (link linkage-decl ...)) ([link-binding (link-id : tagged-sig-id)] [tagged-link-id (tag id link-id) link-id] [linkage-decl ((link-binding ...) unit-expr tagged-link-id)])]{ Links several units into one new compound unit without immediately invoking any of the linked units. The @scheme[unit-expr]s in the @scheme[link] clause determine the units to be linked in creating the compound unit. The @scheme[unit-expr]s are evaluated when the @scheme[compound-unit] form is evaluated. The @scheme[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 @scheme[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 @scheme[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 @scheme[link-id] name. Specifically, a @scheme[link-id] is bound by the @scheme[import] clause or the left-hand part of an declaration in the @scheme[link] clause. A bound @scheme[link-id] is referenced in the right-hand part of a declaration in the @scheme[link] clause or by the @scheme[export] clause. The left-hand side of a @scheme[link] declaration gives names to each expected export of the unit produced by the corresponding @scheme[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 @exnraise[exn:fail:contract] when the @scheme[compound-unit] form is evaluated. The right-hand side of a @scheme[link] declaration specifies the imports to be supplied to the unit produced by the corresponding @scheme[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 @exnraise[exn:fail:contract] when the @scheme[compound-unit] form is evaluated. Each @scheme[link-id] supplied as an import must be bound either in the @scheme[import] clause or in some declaration within the @scheme[link] clause. The order of declarations in the @scheme[link] clause determines the order of invocation of the linked units. When the compound unit is invoked, the unit produced by the first @scheme[unit-expr] is invoked first, then the second, and so on. If the order specified in the @scheme[link] clause is inconsistent with @scheme[init-depend] declarations of the actual units, then the @exnraise[exn:fail:contract] when the @scheme[compound-unit] form is evaluated.} @; ------------------------------------------------------------------------ @section[#:tag "linkinference"]{Inferred Linking} @defform[ #:literals (import export) (define-unit unit-id (import tagged-sig-spec ...) (export tagged-sig-spec ...) init-depends-decl unit-body-expr-or-defn ...) ]{ Binds @scheme[unit-id] to both a unit and static information about the unit. Evaluating a reference to an @scheme[unit-id] bound by @scheme[define-unit] produces a unit, just like evaluating an @scheme[id] bound by @scheme[(define id (unit ...))]. In addition, however, @scheme[unit-id] can be used in @scheme[compound-unit/infer]. See @scheme[unit] for information on @scheme[tagged-sig-spec], @scheme[init-depends-decl], and @scheme[unit-body-expr-or-defn].} @defform/subs[ #:literals (import export :) (compound-unit/infer (import tagged-infer-link-import ...) (export tagged-infer-link-export ...) (link infer-linkage-decl ...)) ([tagged-infer-link-import tagged-sig-id (link-id : tagged-sig-id)] [tagged-infer-link-export (tag id infer-link-export) infer-link-export] [infer-link-export link-id sig-id] [infer-linkage-decl ((link-binding ...) unit-id tagged-link-id) unit-id])]{ Like @scheme[compound-unit]. Syntactically, the difference between @scheme[compound-unit] and @scheme[compound-unit/infer] is that the @scheme[unit-expr] for a linked unit is replaced with a @scheme[unit-id], where a @scheme[unit-id] is bound by @scheme[define-unit] (or one of the other unit-binding forms that we introduce later in this section). Furthermore, an import can name just a @scheme[sig-id] without locally binding a @scheme[link-id], and an export can be based on a @scheme[sig-id] instead of a @scheme[link-id], and a declaration in the @scheme[link] clause can be simply a @scheme[unit-id] with no specified exports or imports. The @scheme[compound-unit/infer] form expands to @scheme[compound-unit] by adding @scheme[sig-ids] as needed to the @scheme[import] clause, by replacing @scheme[sig-id]s in the @scheme[export] clause by @scheme[link-id]s, and by completing the declarations of the @scheme[link] clause. This completion is based on static information associated with each @scheme[unit-id]. 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 @defterm{distinct} only if when they share no ancestor through @scheme[extends]. The long form of a @scheme[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 @scheme[compound-unit], the @scheme[compound-unit/infer] form produces a (compound) unit without statically binding information about the result unit's imports and exports. That is, @scheme[compound-unit/infer] consumes static information, but it does not generate it. Two additional forms, @scheme[define-compound-unit] and @scheme[define-compound-unit/infer], generate static information (where the former does not consume static information).} @defform[ #:literals (import export link) (define-compound-unit id (import link-binding ...) (export tagged-link-id ...) (link linkage-decl ...)) ]{ Like @scheme[compound-unit], but binds static information about the compound unit like @scheme[define-unit].} @defform[ #:literals (import export link) (define-compound-unit/infer id (import link-binding ...) (export tagged-infer-link-export ...) (link infer-linkage-decl ...)) ]{ Like @scheme[compound-unit/infer], but binds static information about the compound unit like @scheme[define-unit].} @defform[ #:literals (import export) (define-unit-binding unit-id unit-expr (import tagged-sig-spec ...+) (export tagged-sig-spec ...+) init-depends-decl) ]{ Like @scheme[define-unit], but the unit implementation is determined from an existing unit produced by @scheme[unit-expr]. The imports and exports of the unit produced by @scheme[unit-expr] must be consistent with the declared imports and exports, otherwise the @exnraise[exn:fail:contract] when the @scheme[define-unit-binding] form is evaluated.} @defform[(invoke-unit/infer unit-id)]{ Like @scheme[invoke-unit], but uses static information associated with @scheme[unit-id] to infer which imports must be assembled from the current context.} @defform[(define-values/invoke-unit/infer unit-id)]{ Like @scheme[define-values/invoke-unit], but uses static information associated with @scheme[unit-id] to infer which imports must be assembled from the current context and what exports should be bound by the definition.} @; ------------------------------------------------------------------------ @section{Generating A Unit from Context} @defform[ (unit-from-context tagged-sig-spec) ]{ Creates a unit that implements an interface using bindings in the enclosing environment. The generated unit is essentially the same as @schemeblock[ (unit (import) (export tagged-sig-spec) (define id expr) ...) ] for each @scheme[id] that must be defined to satisfy the exports, and each corresponding @scheme[expr] produces the value of @scheme[id] in the environment of the @scheme[unit-from-context] expression. (The unit cannot be written as above, however, since each @scheme[id] definition within the unit shadows the binding outside the @scheme[unit] form.) See @scheme[unit] for the grammar of @scheme[tagged-sig-spec].} @defform[ (define-unit-from-context id tagged-sig-spec) ]{ Like @scheme[unit-from-context], in that a unit is constructed from the enclosing environment, and like @scheme[define-unit], in that @scheme[id] is bound to static information to be used later with inference.} @; ------------------------------------------------------------------------ @section{Structural Matching} @defform[ #:literals (import export) (unit/new-import-export (import tagged-sig-spec ...) (export tagged-sig-spec ...) init-depends-decl ((tagged-sig-spec ...) unit-expr tagged-sig-spec)) ]{ Similar to @scheme[unit], except the body of the unit is determined by an existing unit produced by @scheme[unit-expr]. The result is a unit that whose implementation is @scheme[unit-expr], but whose imports, exports, and initialization dependencies are as in the @scheme[unit/new-import-export] form (instead of as in the unit produced by @scheme[unit-expr]). The final clause of the @scheme[unit/new-import-export] form determines the connection between the old and new imports and exports. The connection is similar to the way that @scheme[compound-unit] propagates imports and exports; the difference is that the connection between @scheme[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 @scheme[tagged-sig-spec] on the right-hand side of the link clause need not apppear as a @scheme[tagged-sig-spec] in the @scheme[import] clause, but each of the bindings implied by the linking @scheme[tagged-sig-spec] must be implied by some @scheme[tagged-sig-spec] in the @scheme[import] clause. Similarly, each of the bindings implied by an @scheme[export] @scheme[tagged-sig-spec] must be implied by some left-hand-side @scheme[tagged-sig-spec] in the linking clause.} @defform[ #:literals (import export) (define-unit/new-import-export unit-id (import tagged-sig-spec ...) (export tagged-sig-spec ...) init-depends-decl ((tagged-sig-spec ...) unit-expr tagged-sig-spec)) ]{ Like @scheme[unit/new-import-export], but binds static information to @scheme[unit-id] like @scheme[define-unit].} @; ------------------------------------------------------------------------ @section[#:tag "define-sig-form"]{Extending the Syntax of Signatures} @defform*[ [(define-signature-form sig-form-id expr) (define-signature-form (sig-form-id id) body ...+)] ] Binds @scheme[sig-form-id] for use within a @scheme[define-signature] form. In the first form, the result of @scheme[expr] must be a transformer procedure. In the second form, @scheme[sig-form-id] is bound to a transformer procedure whose argument is @scheme[id] and whose body is the @scheme[body]s. The result of the transformer must be a list of syntax objects, which are substituted for a use of @scheme[sig-form-id] in a @scheme[define-signature] expansion. (The result is a list so that the transformer can produce multiple declarations; @scheme[define-signature] has no splicing @scheme[begin] form.)} @defform/subs[ (struct id (field ...) option ...) ([field id [id #:mutable]] [option #:mutable #:omit-constructor #:omit-define-syntaxes #:omit-define-values])]{ For use with @scheme[define-signature]. The expansion of a @scheme[struct] signature form includes all of the identifiers that would be bound by @scheme[(define-struct id (field ...) option ...)], where the extra option @scheme[#:omit-constructor] omits the @schemeidfont{make-}@scheme[id] identifier.} @; ------------------------------------------------------------------------ @section{Unit Utilities} @defproc[(unit? [v any/c]) boolean?]{ Returns @scheme[#t] if @scheme[v] is a unit, @scheme[#f] otherwise.} @defform[(provide-signature-elements sig-spec ...)]{ Expands to a @scheme[provide] of all identifiers implied by the @scheme[sig-spec]s. See @scheme[unit] for the grammar of @scheme[sig-spec].} @; ------------------------------------------------------------------------ @section[#:tag "single-unit"]{Single-Unit Modules} When @schememodname[scheme/unit] is used as a language name with @schemefont{#lang}, the module body is treated as a unit body. The body must match the following @scheme[_module-body] grammar: @schemegrammar*[ #:literals (import export require begin) [module-body (code:line require-decl ... (import tagged-sig-expr ...) (export tagged-sig-expr ...) init-depends-decl unit-body-expr-or-defn ...)] [require-decl (require require-spec ...) (begin require-decl ...) derived-require-form]] After any number of @scheme[_require-decl]s, the content of the module is the same as a @scheme[unit] body. The resulting unit is exported as @scheme[_base]@schemeidfont["@"], where @scheme[_base] is derived from the enclosing module's name (i.e., its symbolic name, or its path without the directory and file suffix). If the module name ends in @schemeidfont{-unit}, then @scheme[_base] corresponds to the module name before @schemeidfont{-unit}. Otherwise, the module name serves as @scheme[_base]. @; ------------------------------------------------------------------------ @section{Single-Signature Modules} @defmodulelang[scheme/signature]{The @schememodname[scheme/signature] language treats a module body as a unit signature.} The body must match the following @scheme[_module-body] grammar: @schemegrammar*[ #:literals (require) [module-body (code:line (require require-spec ...) ... sig-spec ...)] ] See @secref["creatingunits"] for the grammar of @scheme[_sig-spec]. Unlike the body of a @schememodname[scheme/unit] module, a @scheme[require] in a @schememodname[scheme/signature] module must be a literal use of @scheme[require]. The resulting signature is exported as @scheme[_base]@schemeidfont["^"], where @scheme[_base] is derived from the enclosing module's name (i.e., its symbolic name, or its path without the directory and file suffix). If the module name ends in @schemeidfont{-sig}, then @scheme[_base] corresponds to the module name before @schemeidfont{-sig}. Otherwise, the module name serves as @scheme[_base]. @; ---------------------------------------------------------------------- @section{Transformer Helpers} @defmodule[scheme/unit-exptime #:use-sources (mzlib/unit-exptime)] The @schememodname[scheme/unit-exptime] library provides procedures that are intended for use by macro transformers. In particular, the library is typically imported using @scheme[for-syntax] into a module that defines macro with @scheme[define-syntax]. @defproc[(unit-static-signatures [unit-identifier identifier?] [err-syntax syntax?]) (values (list/c (cons/c (or/c symbol? false/c) identifier?)) (list/c (cons/c (or/c symbol? false/c) identifier?)))]{ If @scheme[unit-identifier] is bound to static unit information via @scheme[define-unit] (or other such forms), the result is two values. The first value is for the unit's imports, and the second is for the unit's exports. Each result value is a list, where each list element pairs a symbol or @scheme[#f] with an identifier. The symbol or @scheme[#f] indicates the import's or export's tag (where @scheme[#f] indicates no tag), and the identifier indicates the binding of the corresponding signature. If @scheme[unit-identifier] is not bound to static unit information, then the @exnraise[exn:fail:syntax]. In that case, the given @scheme[err-syntax] argument is used as the source of the error, where @scheme[unit-identifer] is used as the detail source location.} @defproc[(signature-members [sig-identifier identifier?] [err-syntax syntax?]) (values (or/c identifier? false/c) (listof identifier?) (listof identifier?) (listof identifier?))]{ If @scheme[sig-identifier] is bound to static unit information via @scheme[define-signature] (or other such forms), the result is four values: @itemize{ @item{an identifier or @scheme[#f] indicating the signature (of any) that is extended by the @scheme[sig-identifier] binding;} @item{a list of identifiers representing the variables supplied/required by the signature;} @item{a list of identifiers for variable definitions in the signature (i.e., variable bindings that are provided on import, but not defined by units that implement the signature); and} @item{a list of identifiers with syntax definitions in the signature.} } If @scheme[sig-identifier] is not bound to a signature, then the @exnraise[exn:fail:syntax]. In that case, the given @scheme[err-syntax] argument is used as the source of the error, where @scheme[sig-identifier] is used as the detail source location.}