units chapter in guide

svn: r9907
This commit is contained in:
Matthew Flatt 2008-05-20 20:16:44 +00:00
parent bd714552b1
commit 5ca718b35e
8 changed files with 656 additions and 29 deletions

View File

@ -4,6 +4,7 @@
scribble/decode
scribble/srcdoc
(for-syntax scheme/base
scheme/path
syntax/path-spec))
(provide include-extracted)
@ -28,7 +29,9 @@
(raise-syntax-error #f "expected a literal regular expression as the second argument" stx #'regexp-s))
(let ([s-exp
(parameterize ([current-namespace (make-base-namespace)]
[read-accept-reader #t])
[read-accept-reader #t]
[current-load-relative-directory
(path-only path)])
(expand
(with-input-from-file path
(lambda ()

View File

@ -50,7 +50,7 @@
(define-syntax (schememod stx)
(syntax-case stx ()
[(_ lang rest ...)
[(_ #:file filename lang rest ...)
(with-syntax ([modtag (datum->syntax
#'here
`(unsyntax (make-element
@ -60,8 +60,18 @@
(as-modname-link
',#'lang
(to-element ',#'lang)))))
#'lang)])
#'(schemeblock modtag rest ...))]))
#'lang)]
[(file ...)
(if (syntax-e #'filename)
(list
(datum->syntax
#'filename
`(code:comment (unsyntax (t "In \"" ,#'filename "\":")))
#'filename))
null)])
(syntax/loc stx (schemeblock file ... modtag rest ...)))]
[(_ lang rest ...)
(syntax/loc stx (schememod #:file #f lang rest ...))]))
(define (to-element/result s)
(make-element "schemeresult" (list (to-element/no-color s))))

View File

@ -5,7 +5,7 @@
@title[#:tag "bytestrings"]{Bytes and Byte Strings}
A @defterm{byte} is an exact integer between @scheme[0] and
A @deftech{byte} is an exact integer between @scheme[0] and
@scheme[255], inclusive. The @scheme[byte?] predicate recognizes
numbers that represent bytes.

View File

@ -44,9 +44,7 @@ precise details to @|MzScheme| and other reference manuals.
@include-section["class.scrbl"]
@; ----------------------------------------------------------------------
@section[#:tag "units"]{Units@aux-elem{ (Components)}}
@include-section["unit.scrbl"]
@; ----------------------------------------------------------------------
@section[#:tag "threads"]{Threads}

View File

@ -5,7 +5,8 @@
mzlib/process
"guide-utils.ss"
(for-label scheme/tcp
scheme/serialize))
scheme/serialize
scheme/port))
@(define io-eval (make-base-eval))
@ -336,5 +337,80 @@ deserialization identifier is accessed reflectively when a value is
deserialized.
@; ----------------------------------------------------------------------
@section{Bytes versus Characters}
@section{Bytes, Characters, and Encodings}
Functions like @scheme[read-line], @scheme[read], @scheme[display],
and @scheme[write] all work in terms of @tech{characters} (which
correspond to Unicode scalar values). Conceptually, they are
implemented in terms of @scheme[read-char] and @scheme[write-char].
More primitively, ports read and write @tech{bytes}, instead of
@tech{characters}. The functions @scheme[read-byte] and
@scheme[write-byte] read and write raw bytes. Other functions, such as
@scheme[read-bytes-line], build on top of byte operations instead of
character operations.
In fact, the @scheme[read-char] and @scheme[write-char] functions are
conceptually implemented in terms of @scheme[read-byte] and
@scheme[write-byte]. When a single byte's value is less than 128, then
it corresponds to an ASCII character. Any other byte is treated as
part of a UTF-8 sequence, where UTF-8 is a particular standard way of
encoding Unicode scalar values in bytes (which has the nice property
that ASCII characters are encoded as themselves). Thus, a single
@scheme[read-char] may call @scheme[read-byte] multiple times, and a
single @scheme[write-char] may generate multiple output bytes.
The @scheme[read-char] and @scheme[write-char] operations
@emph{always} use a UTF-8 encoding. If you have a text stream that
uses a different encoding, or if you want to generate a text stream in
a different encoding, use @scheme[reencode-input-port] or
@scheme[reencode-output-port]. The @scheme[reencode-input-port]
function converts an input stream from an encoding that you specify
into a UTF-8 stream; that way, @scheme[read-char] sees UTF-8
encodings, even though the original used a different encoding. Beware,
however, that @scheme[read-byte] also sees the re-encoded data,
instead of the original byte stream.
@; ----------------------------------------------------------------------
@section{I/O Patterns}
@(begin
(define port-eval (make-base-eval))
(interaction-eval #:eval port-eval (require scheme/port)))
If you want to process individual lines of a file, then you can use
@scheme[for] with @scheme[in-lines]:
@interaction[
(define (upcase-all in)
(for ([l (in-lines in)])
(display (string-upcase l))
(newline)))
(upcase-all (open-input-string
(string-append
"Hello, World!\n"
"Can you hear me, now?")))
]
If you want to determine whether ``hello'' appears a file, then you
could search separate lines, but it's even easier to simply apply a
regular expression (see @secref["regexp"]) to the stream:
@interaction[
(define (has-hello? in)
(regexp-match? #rx"hello" in))
(has-hello? (open-input-string "hello"))
(has-hello? (open-input-string "goodbye"))
]
If you want to copy one port into another, use @scheme[copy-port] from
@schememodname[scheme/port], which efficiently transfers large blocks
when lots of data is available, but also transfers small blocks
immediately if that's all that is available:
@interaction[
#:eval port-eval
(define o (open-output-string))
(copy-port (open-input-string "broom") o)
(get-output-string o)
]

View File

@ -31,7 +31,7 @@ string can be prefixed with @litchar{#px}, as in @scheme[#px"abc"],
for a slightly extended syntax of patterns within the string.
Most of the characters in a @tech{regexp} pattern are meant to match
occurrences of themselves in the text string. Thus, the pattern
occurrences of themselves in the @tech{text string}. Thus, the pattern
@scheme[#rx"abc"] matches a string that contains the characters
@litchar{a}, @litchar{b}, and @litchar{c} in succession. Other
characters act as @deftech{metacharacters}, and some character
@ -87,8 +87,8 @@ The @scheme[regexp-quote] function is useful when building a composite
@section[#:tag "regexp-match"]{Matching Regexp Patterns}
The @scheme[regexp-match-positions] function takes a @tech{regexp}
pattern and a text string, and it returns a match if the regexp
matches (some part of) the text string, or @scheme[#f] if the regexp
pattern and a @tech{text string}, and it returns a match if the regexp
matches (some part of) the @tech{text string}, or @scheme[#f] if the regexp
did not match the string. A successful match produces a list of
@deftech{index pairs}.
@ -113,7 +113,7 @@ later, we will see how a single match operation can yield a list of
@tech{submatch}es.
The @scheme[regexp-match-positions] function takes optional third and
fourth arguments that specify the indices of the text string within
fourth arguments that specify the indices of the @tech{text string} within
which the matching should take place.
@interaction[
@ -124,7 +124,7 @@ which the matching should take place.
]
Note that the returned indices are still reckoned relative to the full
text string.
@tech{text string}.
The @scheme[regexp-match] function is like
@scheme[regexp-match-positions], but instead of returning index pairs,
@ -135,6 +135,41 @@ it returns the matching substrings:
(regexp-match #rx"needle" "hay needle stack")
]
When @scheme[regexp-match] is used with byte-string regexp, the result
is a matching byte substring:
@interaction[
(regexp-match #rx#"needle" #"hay needle stack")
]
@margin-note{A byte-string regexp can be applied to a string, and a
string regexp can be applied to a byte string. In both
cases, the result is a byte string. Internally, all
regexp matching is in terms of bytes, and a string regexp
is expanded to a regexp that matches UTF-8 encodings of
characters. For maximum efficiency, use byte-string
matching instead of string, since matching bytes directly
avoids UTF-8 encodings.}
If you have data that is in a port, there's no need to first read it
into a string. Functions like @scheme[regexp-match] can match on the
port directly:
@interaction[
(define-values (i o) (make-pipe))
(write "hay needle stack" o)
(close-output-port o)
(regexp-match #rx#"needle" i)
]
The @scheme[regexp-match?] function is like
@scheme[regexp-match-positions], but simply returns a boolean
indicating whether the match succeeded:
@interaction[
(regexp-match? #rx"brain" "bird")
(regexp-match? #rx"needle" "hay needle stack")
]
The @scheme[regexp-split] function takes two arguments, a
@tech{regexp} pattern and a text string, and it returns a list of

View File

@ -0,0 +1,512 @@
#lang scribble/doc
@(require scribble/manual
scribble/eval
"guide-utils.ss"
(for-label scheme/unit
scheme/class))
@(define toy-eval (make-base-eval))
@(interaction-eval #:eval toy-eval (require scheme/unit))
@(define-syntax-rule (schememod/eval [pre ...] form more ...)
(begin
(schememod pre ... form more ...)
(interaction-eval #:eval toy-eval form)))
@title[#:tag "units" #:style 'toc]{Units@aux-elem{ (Components)}}
@deftech{Units} organize a program into separately compilable and
reusable @deftech{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 called to
evaluate its expressions given actual arguments for its formal
parameters, a unit is @deftech{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 @italic{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.
@local-table-of-contents[]
@; ----------------------------------------
@section{Signatures and Units}
The interface of a unit is described in terms of
@deftech{signatures}. Each signature is defined (normally within a
@scheme[module]) using @scheme[define-signature]. For example, the
following signature, placed in a @filepath{toy-factory-sig.ss} file,
describes the exports of a component that implements a toy factory:
@margin-note{By convention, signature names with @litchar{^}.}
@schememod/eval[[#:file
"toy-factory-sig.ss"
scheme]
(define-signature toy-factory^
(build-toys (code:comment (integer? -> (listof toy?)))
repaint (code:comment (toy? symbol? -> toy?))
toy? (code:comment (any/c -> boolean?))
toy-color)) (code:comment (toy? -> symbol?))
(provide toy-factory^)
]
An implementation of the @scheme[toy-factory^] signature is written
using @scheme[define-unit] with an @scheme[export] clause that names
@scheme[toy-factory^]:
@margin-note{By convention, unit names with @litchar["@"].}
@schememod/eval[[#:file
"simple-factory-unit.ss"
scheme
(require "toy-factory-sig.ss")]
(define-unit simple-factory@
(import)
(export toy-factory^)
(printf "Factory started.\n")
(define-struct toy (color) #:transparent)
(define (build-toys n)
(for/list ([i (in-range n)])
(make-toy 'blue)))
(define (repaint t col)
(make-toy col)))
(provide simple-factory@)
]
The @scheme[toy-factory^] signature also could be referenced by a unit
that needs a toy factory to implement something else. In that case,
@scheme[toy-factory^] would be named in an @scheme[import] clause.
For example, a toy store would get toys from a toy factory. (Suppose,
for the sake of an example with interesting features, that the store
is willing to sell only toys in a particular color.)
@schememod/eval[[#:file
"toy-store-sig.ss"
scheme]
(define-signature toy-store^
(store-color (code:comment (-> symbol?))
stock! (code:comment (integer? -> void?))
get-inventory)) (code:comment (-> (listof toy?)))
(provide toy-store^)
]
@schememod/eval[[#:file
"toy-store-unit.ss"
scheme
(require "toy-store-sig.ss"
"toy-factory-sig.ss")]
(define-unit toy-store@
(import toy-factory^)
(export toy-store^)
(define inventory null)
(define (store-color) 'green)
(define (maybe-repaint t)
(if (eq? (toy-color t) (store-color))
t
(repaint t (store-color))))
(define (stock! n)
(set! inventory
(append inventory
(map maybe-repaint
(build-toys n)))))
(define (get-inventory) inventory))
(provide toy-store@)
]
Note that @filepath{toy-store-unit.ss} imports
@filepath{toy-factory-sig.ss}, but not
@filepath{simple-factory-unit.ss}. Consequently, the
@scheme[toy-store@] unit relies only on the specification of a toy
factory, not on a specific implementation.
@; ----------------------------------------
@section{Invoking Units}
The @scheme[simple-factory@] unit has no imports, so it can be
@tech{invoked} directly using @scheme[invoke-unit]:
@interaction[
#:eval toy-eval
(eval:alts (require "simple-factory-unit.ss") (void))
(invoke-unit simple-factory@)
]
The @scheme[invoke-unit] form does not make the body definitions
available, however, so we cannot build any toys with this factory. The
@scheme[define-values/invoke-unit] form binds the identifiers of a
signature to the values supplied by a unit (to be @tech{invoked}) that
implements the signature:
@interaction[
#:eval toy-eval
(define-values/invoke-unit/infer simple-factory@)
(build-toys 3)
]
Since @scheme[simple-factory@] exports the @scheme[toy-factory^]
signature, each identifier in @scheme[toy-factory^] is defined by the
@scheme[define-values/invoke-unit/infer] form. The
@schemeidfont{/infer} part of the form name indicates that the
identifiers bound by the declaration are inferred from
@scheme[simple-factory@].
Now that the identifiers in @scheme[toy-factory^] are defined, we can
also invoke @scheme[toy-store@], which imports @scheme[toy-factory^]
to produce @scheme[toy-store^]:
@interaction[
#:eval toy-eval
(eval:alts (require "toy-store-unit.ss") (void))
(define-values/invoke-unit/infer toy-store@)
(get-inventory)
(stock! 2)
(get-inventory)
]
Again, the @schemeidfont{/infer} part
@scheme[define-values/invoke-unit/infer] determines that
@scheme[toy-store@] imports @scheme[toy-factory^], and so it supplies
the top-level bindings that match the names in @scheme[toy-factory^]
as imports to @scheme[toy-store@].
@; ----------------------------------------
@section{Linking Units}
We can make our toy economy more efficient by having toy factories
that cooperate with stores, creating toys that do not have to be
repainted. Instead, the toys are always created using the store's
color, which the factory gets by importing @scheme[toy-store^]:
@schememod/eval[[#:file
"store-specific-factory-unit.ss"
scheme
(require "toy-factory-sig.ss")]
(define-unit store-specific-factory@
(import toy-store^)
(export toy-factory^)
(define-struct toy () #:transparent)
(define (toy-color t) (store-color))
(define (build-toys n)
(for/list ([i (in-range n)])
(make-toy)))
(define (repaint t col)
(error "cannot repaint")))
(provide store-specific-factory@)
]
To invoke @scheme[store-specific-factory@], we need
@scheme[toy-store^] bindings to supply to the unit. But to get
@scheme[toy-store^] bindings by invoking @scheme[toy-store@], we will
need a toy factory! The unit implementations are mutually dependent,
and we cannot invoke either before the other.
The solution is to @deftech{link} the units together, and then we can
invoke the combined units. The @scheme[define-compound-unit/infer] form
links any number of units to form a combined unit. It can propagate
imports and exports from the linked units, and it can satisfy each
unit's imports using the exports of other linked units.
@interaction[
#:eval toy-eval
(eval:alts (require "store-specific-factory-unit.ss") (void))
(define-compound-unit/infer toy-store+factory@
(import)
(export toy-factory^ toy-store^)
(link store-specific-factory@
toy-store@))
]
The overall result above is a unit @scheme[toy-store+factory@] that
exports both @scheme[toy-factory^] and @scheme[toy-store^]. The
connection between @scheme[store-specific-factory@] and
@scheme[toy-store@] is inferred from the signatures that each imports
and exports.
This unit has no imports, so we can always invoke it:
@interaction[
#:eval toy-eval
(define-values/invoke-unit/infer toy-store+factory@)
(stock! 2)
(get-inventory)
(map toy-color (get-inventory))
]
@; ----------------------------------------
@section{First-Class Units}
The @scheme[define-unit] form combines @scheme[define] with a
@scheme[unit] form, similar to the way that @scheme[(define (f x)
....)] combines @scheme[define] followed by an identifier with an
implicit @scheme[lambda].
Expanding the shorthand, the definition of @scheme[toy-store@] could
almost be written as
@schemeblock[
(define toy-store@
(unit
(import toy-factory^)
(export toy-store^)
(define inventory null)
(define (store-color) 'green)
....))
]
A difference between this expansion and @scheme[define-unit] is that
the imports and exports of @scheme[toy-store@] cannot be
inferred. That is, besides combining @scheme[define] and
@scheme[unit], @scheme[define-unit] attaches static information to the
defined identifier so that its signature information is available
statically to @scheme[define-values/invoke-unit/infer] and other
forms.
Despite the drawback of losing static signature information,
@scheme[unit] can be useful in combination with other forms that work
with first-class values. For example, we could wrap a @scheme[unit]
that creates a toy store in a @scheme[lambda] to supply the store's
color:
@schememod/eval[[#:file
"toy-store-maker.ss"
scheme
(require "toy-store-sig.ss"
"toy-factory-sig.ss")]
(define toy-store@-maker
(lambda (the-color)
(unit
(import toy-factory^)
(export toy-store^)
(define inventory null)
(define (store-color) the-color)
(code:comment #, @t{the rest is the same as before})
(define (maybe-repaint t)
(if (eq? (toy-color t) (store-color))
t
(repaint t (store-color))))
(define (stock! n)
(set! inventory
(append inventory
(map maybe-repaint
(build-toys n)))))
(define (get-inventory) inventory))))
(provide toy-store@-maker)
]
To invoke a unit created by @scheme[toy-store@-maker], we must use
@scheme[define-values/invoke-unit], instead of the
@schemeidfont{/infer} variant:
@interaction[
#:eval toy-eval
(eval:alts (require "simple-factory-unit.ss") (void))
(define-values/invoke-unit/infer simple-factory@)
(eval:alts (require "toy-store-maker.ss") (void))
(define-values/invoke-unit (toy-store@-maker 'purple)
(import toy-factory^)
(export toy-store^))
(stock! 2)
(get-inventory)
]
In the @scheme[define-values/invoke-unit] form, the @scheme[(import
toy-factory^)] line takes bindings from the current context that match
the names in @scheme[toy-factory^] (the ones that we created by
invoking @scheme[simple-factory@]), and it supplies them as imports to
@scheme[toy-store@]. The @scheme[(export toy-store^)] clause indicates
that the unit produced by @scheme[toy-store@-maker] will export
@scheme[toy-store^], and the names from that signature are defined
after invoking the unit.
To link a unit from @scheme[toy-store@-maker], we can use the
@scheme[compound-unit] form:
@interaction[
#:eval toy-eval
(eval:alts (require "store-specific-factory-unit.ss") (void))
(define toy-store+factory@
(compound-unit
(import)
(export TF TS)
(link [((TF : toy-factory^)) store-specific-factory@ TS]
[((TS : toy-store^)) toy-store@ TF])))
]
This @scheme[compound-unit] form packs a lot of information into one
place. The left-hand-side @scheme[TF] and @scheme[TS] in the
@scheme[link] clause are binding indentifiers. The identifier
@scheme[TF] is essentially bound to the elements of
@scheme[toy-factory^] as implemented by
@scheme[store-specific-factory@]. The identifier @scheme[TS] is
similarly bound to the elements of @scheme[toy-store^] as implemented
by @scheme[toy-store@]. Meanwhile, the elements bound to @scheme[TS]
are supplied as imports for @scheme[store-specific-factory@], since
@scheme[TS] follows @scheme[store-specific-factory@]. The elements
bound to @scheme[TF] are similarly supplied to
@scheme[toy-store@]. Finally, @scheme[(export TF TS)] indicates that
the elements bound to @scheme[TF] and @scheme[TS] are exported from
the compound unit.
The above @scheme[compound-unit] form uses
@scheme[store-specific-factory@] as a first-class unit, even though
its information could be inferred. Every unit can be used as a
first-class unit, in addition to its use in inference contexts. Also,
various forms let a programmer bridge the gap between inferred and
first-class worlds. For example, @scheme[define-unit-binding] binds a
new identifier to the unit produced by an arbitrary expression; it
statically associates signature information to the identifier, and it
dynamically checks the signatures against the first-class unit
produced by the expression.
@; ----------------------------------------
@section{Whole-@scheme[module] Signatures and Units}
In programs that use units, modules like @filepath{toy-factory-sig.ss}
and @filepath{simple-factory-unit.ss} are common. The
@scheme[scheme/signature] and @scheme[scheme/unit] module names can be
used as languages to avoid much of the boilerplate module, signature,
and unit declaration text.
For example, @filepath{toy-factory-sig.ss} can be written as
@schememod[
scheme/signature
build-toys (code:comment (integer? -> (listof toy?)))
repaint (code:comment (toy? symbol? -> toy?))
toy? (code:comment (any/c -> boolean?))
toy-color (code:comment (toy? -> symbol?))
]
The signature @scheme[toy-factory^] is automatically provided from the
module, inferred from the filename @filepath{toy-factory-sig.ss} by
replacing the @filepath{-sig.ss} suffix with @schemeidfont{^}.
Similarly, @filepath{simple-factory-unit.ss} module can be written
@schememod[
scheme/unit
(require "toy-factory-sig.ss")
(import)
(export toy-factory^)
(printf "Factory started.\n")
(define-struct toy (color) #:transparent)
(define (build-toys n)
(for/list ([i (in-range n)])
(make-toy 'blue)))
(define (repaint t col)
(make-toy col))
]
The unit @scheme[simple-factory@] is automatically provided from the
module, inferred from the filename @filepath{simple-factory-unit.ss} by
replacing the @filepath{-unit.ss} suffix with @schemeidfont["@"].
@; ----------------------------------------
@section{@scheme[unit] versus @scheme[module]}
As a form for modularity, @scheme[unit] complements @scheme[module]:
@itemize[
@item{The @scheme[module] form is primarily for managing a universal
namespace. For example, it allows a code fragment to refer
specifically to the @scheme[car] operation from
@schememodname[scheme/base]---the one that extracts the first
element of an instance of the built-in pair datatype---as
opposed to any number of other functions with the name
@scheme[car]. In other word, the @scheme[module] construct lets
you refer to @emph{the} binding that you want.}
@item{The @scheme[unit] form is for parameterizing a code fragment
with respect to most any kind of run-time value. For example,
it allows a code fragement for work with a @scheme[car]
function that accepts a single argument, where the specific
function is determined later by linking the fragment to
another. In other words, the @scheme[unit] construct lets you
refer to @emph{a} binding that meets some specification.}
]
The @scheme[lambda] and @scheme[class] forms, among others, also allow
paremetrization of code with respect to values that are chosen
later. In principle, any of those could be implemented in terms of any
of the others. In practice, each form offers certain
conveniences---such as allowing overriding of methods or especially
simple application to values---that make them suitable for different
purposes.
The @scheme[module] form is more fundamental that the others, in a
sense. After all, a program fragment cannot reliably refer to
@scheme[lambda], @scheme[class], or @scheme[unit] form without the
namespace management provided by @scheme[module]. At the same time,
because namespace management is closely related to separate expansion
and compilation, @scheme[module] boundaries end up as
separate-compilation boundaries in a way that prohibits mutual
dependencies among fragments. For similar reasons, @scheme[module]
does not separate interface from implementation.
Use @scheme[unit] when @scheme[module] by itself almost works, but
when separately compiled pieces must refer to each other, or when you
want a stronger separation between @defterm{interface} (i.e., the
parts that need to be known at expansion and compilation time) and
@defterm{implementation} (i.e., the run-time parts). More generally,
use @scheme[unit] when you need to parameterize code over functions,
datatypes, and classes, and when the parameterized code itself
provides definitions to be linked with other parameterized code.

View File

@ -19,20 +19,13 @@
@title[#:tag "mzlib:unit" #:style 'toc]{Units}
@deftech{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 @italic{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.
@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