units chapter in guide
svn: r9907
This commit is contained in:
parent
bd714552b1
commit
5ca718b35e
|
@ -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 ()
|
||||
|
|
|
@ -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))))
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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)
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
512
collects/scribblings/guide/unit.scrbl
Normal file
512
collects/scribblings/guide/unit.scrbl
Normal 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.
|
||||
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user