units chapter in guide
svn: r9907
This commit is contained in:
parent
bd714552b1
commit
5ca718b35e
|
@ -4,6 +4,7 @@
|
||||||
scribble/decode
|
scribble/decode
|
||||||
scribble/srcdoc
|
scribble/srcdoc
|
||||||
(for-syntax scheme/base
|
(for-syntax scheme/base
|
||||||
|
scheme/path
|
||||||
syntax/path-spec))
|
syntax/path-spec))
|
||||||
|
|
||||||
(provide include-extracted)
|
(provide include-extracted)
|
||||||
|
@ -28,7 +29,9 @@
|
||||||
(raise-syntax-error #f "expected a literal regular expression as the second argument" stx #'regexp-s))
|
(raise-syntax-error #f "expected a literal regular expression as the second argument" stx #'regexp-s))
|
||||||
(let ([s-exp
|
(let ([s-exp
|
||||||
(parameterize ([current-namespace (make-base-namespace)]
|
(parameterize ([current-namespace (make-base-namespace)]
|
||||||
[read-accept-reader #t])
|
[read-accept-reader #t]
|
||||||
|
[current-load-relative-directory
|
||||||
|
(path-only path)])
|
||||||
(expand
|
(expand
|
||||||
(with-input-from-file path
|
(with-input-from-file path
|
||||||
(lambda ()
|
(lambda ()
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
|
|
||||||
(define-syntax (schememod stx)
|
(define-syntax (schememod stx)
|
||||||
(syntax-case stx ()
|
(syntax-case stx ()
|
||||||
[(_ lang rest ...)
|
[(_ #:file filename lang rest ...)
|
||||||
(with-syntax ([modtag (datum->syntax
|
(with-syntax ([modtag (datum->syntax
|
||||||
#'here
|
#'here
|
||||||
`(unsyntax (make-element
|
`(unsyntax (make-element
|
||||||
|
@ -60,8 +60,18 @@
|
||||||
(as-modname-link
|
(as-modname-link
|
||||||
',#'lang
|
',#'lang
|
||||||
(to-element ',#'lang)))))
|
(to-element ',#'lang)))))
|
||||||
#'lang)])
|
#'lang)]
|
||||||
#'(schemeblock modtag rest ...))]))
|
[(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)
|
(define (to-element/result s)
|
||||||
(make-element "schemeresult" (list (to-element/no-color s))))
|
(make-element "schemeresult" (list (to-element/no-color s))))
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
@title[#:tag "bytestrings"]{Bytes and Byte Strings}
|
@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
|
@scheme[255], inclusive. The @scheme[byte?] predicate recognizes
|
||||||
numbers that represent bytes.
|
numbers that represent bytes.
|
||||||
|
|
||||||
|
|
|
@ -44,9 +44,7 @@ precise details to @|MzScheme| and other reference manuals.
|
||||||
|
|
||||||
@include-section["class.scrbl"]
|
@include-section["class.scrbl"]
|
||||||
|
|
||||||
@; ----------------------------------------------------------------------
|
@include-section["unit.scrbl"]
|
||||||
@section[#:tag "units"]{Units@aux-elem{ (Components)}}
|
|
||||||
|
|
||||||
|
|
||||||
@; ----------------------------------------------------------------------
|
@; ----------------------------------------------------------------------
|
||||||
@section[#:tag "threads"]{Threads}
|
@section[#:tag "threads"]{Threads}
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
mzlib/process
|
mzlib/process
|
||||||
"guide-utils.ss"
|
"guide-utils.ss"
|
||||||
(for-label scheme/tcp
|
(for-label scheme/tcp
|
||||||
scheme/serialize))
|
scheme/serialize
|
||||||
|
scheme/port))
|
||||||
|
|
||||||
@(define io-eval (make-base-eval))
|
@(define io-eval (make-base-eval))
|
||||||
|
|
||||||
|
@ -336,5 +337,80 @@ deserialization identifier is accessed reflectively when a value is
|
||||||
deserialized.
|
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.
|
for a slightly extended syntax of patterns within the string.
|
||||||
|
|
||||||
Most of the characters in a @tech{regexp} pattern are meant to match
|
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
|
@scheme[#rx"abc"] matches a string that contains the characters
|
||||||
@litchar{a}, @litchar{b}, and @litchar{c} in succession. Other
|
@litchar{a}, @litchar{b}, and @litchar{c} in succession. Other
|
||||||
characters act as @deftech{metacharacters}, and some character
|
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}
|
@section[#:tag "regexp-match"]{Matching Regexp Patterns}
|
||||||
|
|
||||||
The @scheme[regexp-match-positions] function takes a @tech{regexp}
|
The @scheme[regexp-match-positions] function takes a @tech{regexp}
|
||||||
pattern and a text string, and it returns a match if the regexp
|
pattern and a @tech{text string}, and it returns a match if the regexp
|
||||||
matches (some part of) the text string, or @scheme[#f] 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
|
did not match the string. A successful match produces a list of
|
||||||
@deftech{index pairs}.
|
@deftech{index pairs}.
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ later, we will see how a single match operation can yield a list of
|
||||||
@tech{submatch}es.
|
@tech{submatch}es.
|
||||||
|
|
||||||
The @scheme[regexp-match-positions] function takes optional third and
|
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.
|
which the matching should take place.
|
||||||
|
|
||||||
@interaction[
|
@interaction[
|
||||||
|
@ -124,7 +124,7 @@ which the matching should take place.
|
||||||
]
|
]
|
||||||
|
|
||||||
Note that the returned indices are still reckoned relative to the full
|
Note that the returned indices are still reckoned relative to the full
|
||||||
text string.
|
@tech{text string}.
|
||||||
|
|
||||||
The @scheme[regexp-match] function is like
|
The @scheme[regexp-match] function is like
|
||||||
@scheme[regexp-match-positions], but instead of returning index pairs,
|
@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")
|
(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
|
The @scheme[regexp-split] function takes two arguments, a
|
||||||
@tech{regexp} pattern and a text string, and it returns a list of
|
@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}
|
@title[#:tag "mzlib:unit" #:style 'toc]{Units}
|
||||||
|
|
||||||
@deftech{Units} are used to organize a program into separately
|
@deftech{Units} organize a program into separately compilable and
|
||||||
compilable and reusable components. A unit resembles a procedure in
|
reusable components. The imports and exports of a unit are grouped
|
||||||
that both are first-class values that are used for abstraction. While
|
into a @deftech{signature}, which can include ``static'' information
|
||||||
procedures abstract over values in expressions, units abstract over
|
(such as macros) in addition to placeholders for run-time values.
|
||||||
names in collections of definitions. Just as a procedure is invoked to
|
Units with suitably matching signatures can be @deftech{linked}
|
||||||
evaluate its expressions given actual arguments for its formal
|
together to form a larger unit, and a unit with no imports can be
|
||||||
parameters, a unit is invoked to evaluate its definitions given actual
|
@deftech{invoked} to execute its body.
|
||||||
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.
|
|
||||||
|
|
||||||
@note-lib[scheme/unit #:use-sources (mzlib/unit)]{ The
|
@note-lib[scheme/unit #:use-sources (mzlib/unit)]{ The
|
||||||
@schememodname[scheme/unit] module name can be used as a language name
|
@schememodname[scheme/unit] module name can be used as a language name
|
||||||
|
|
Loading…
Reference in New Issue
Block a user