From 5ca718b35e23277adb065a51e1f98732c23a7f5b Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Tue, 20 May 2008 20:16:44 +0000 Subject: [PATCH] units chapter in guide svn: r9907 --- collects/scribble/extract.ss | 5 +- collects/scribble/manual.ss | 16 +- collects/scribblings/guide/byte-strings.scrbl | 2 +- collects/scribblings/guide/guide.scrbl | 4 +- collects/scribblings/guide/io.scrbl | 80 ++- collects/scribblings/guide/regexp.scrbl | 45 +- collects/scribblings/guide/unit.scrbl | 512 ++++++++++++++++++ collects/scribblings/reference/units.scrbl | 21 +- 8 files changed, 656 insertions(+), 29 deletions(-) create mode 100644 collects/scribblings/guide/unit.scrbl diff --git a/collects/scribble/extract.ss b/collects/scribble/extract.ss index fa1411464c..1d51a912c6 100644 --- a/collects/scribble/extract.ss +++ b/collects/scribble/extract.ss @@ -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 () diff --git a/collects/scribble/manual.ss b/collects/scribble/manual.ss index c644eb3892..4c00bc3f1e 100644 --- a/collects/scribble/manual.ss +++ b/collects/scribble/manual.ss @@ -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)))) diff --git a/collects/scribblings/guide/byte-strings.scrbl b/collects/scribblings/guide/byte-strings.scrbl index 4b555ab680..a9be5cfde9 100644 --- a/collects/scribblings/guide/byte-strings.scrbl +++ b/collects/scribblings/guide/byte-strings.scrbl @@ -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. diff --git a/collects/scribblings/guide/guide.scrbl b/collects/scribblings/guide/guide.scrbl index 3318df54f3..bebb3f6b43 100644 --- a/collects/scribblings/guide/guide.scrbl +++ b/collects/scribblings/guide/guide.scrbl @@ -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} diff --git a/collects/scribblings/guide/io.scrbl b/collects/scribblings/guide/io.scrbl index 5257b9da93..c5905e3f52 100644 --- a/collects/scribblings/guide/io.scrbl +++ b/collects/scribblings/guide/io.scrbl @@ -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) +] diff --git a/collects/scribblings/guide/regexp.scrbl b/collects/scribblings/guide/regexp.scrbl index 714b4487a5..4e5a938e54 100644 --- a/collects/scribblings/guide/regexp.scrbl +++ b/collects/scribblings/guide/regexp.scrbl @@ -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 diff --git a/collects/scribblings/guide/unit.scrbl b/collects/scribblings/guide/unit.scrbl new file mode 100644 index 0000000000..ddc8db72b3 --- /dev/null +++ b/collects/scribblings/guide/unit.scrbl @@ -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. + diff --git a/collects/scribblings/reference/units.scrbl b/collects/scribblings/reference/units.scrbl index 65ce458e16..ef8a0bc3a1 100644 --- a/collects/scribblings/reference/units.scrbl +++ b/collects/scribblings/reference/units.scrbl @@ -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