doc corrections and improvements related to submodules

In particular, add `module+' to the Guide.
This commit is contained in:
Matthew Flatt 2012-03-22 14:46:05 -06:00
parent d4d5ca70fb
commit 876bc6f02b
5 changed files with 191 additions and 59 deletions

View File

@ -7,38 +7,37 @@
(define submodule 'test) (define submodule 'test)
(define run-anyways? #f) (define run-anyways? #f)
(define do-test (define (do-test e [check-suffix? #f])
(match-lambda (match e
[(? string? s) [(? string? s)
(do-test (string->path s))] (do-test (string->path s))]
[(? path? p) [(? path? p)
(define ps (path->string p)) (cond
(cond
[(directory-exists? p) [(directory-exists? p)
(for-each (for-each
(λ (dp) (λ (dp)
(do-test (build-path p dp))) (do-test (build-path p dp) #t))
(directory-list p))] (directory-list p))]
[(and (file-exists? p) [(and (file-exists? p)
(regexp-match #rx"\\.rkt$" ps)) (or (not check-suffix?)
(define fmod `(file ,ps)) (regexp-match #rx#"\\.rkt$" (path->bytes p))))
(define mod `(submod ,fmod ,submodule)) (define mod `(submod ,p ,submodule))
(cond (cond
[(module-declared? mod #t) [(module-declared? mod #t)
(dynamic-require mod #f)] (dynamic-require mod #f)]
[(and run-anyways? (module-declared? fmod #t)) [(and run-anyways? (module-declared? p #t))
(dynamic-require fmod #f)])] (dynamic-require p #f)])]
[(not (file-exists? p)) [(not (file-exists? p))
(error 'test "Given path ~e does not exist" p)])])) (error 'test "Given path ~e does not exist" p)])]))
(command-line (command-line
#:program (short-program+command-name) #:program (short-program+command-name)
#:once-each #:once-each
[("--submodule" "-s") submodule-str [("--submodule" "-s") name
"Determines which submodule to load" "Runs submodule <name> (defaults to `test')"
(set! submodule (string->symbol submodule-str))] (set! submodule (string->symbol name))]
[("--run-if-absent" "-r") [("--run-if-absent" "-r")
"When set, raco test will require the default module if the given submodule is not present." "Require base module if submodule is absent"
(set! run-anyways? #t)] (set! run-anyways? #t)]
#:args files+directories #:args file-or-directory
(for-each do-test files+directories)) (for-each do-test file-or-directory))

View File

@ -1,5 +1,6 @@
#lang scribble/doc #lang scribble/doc
@(require scribble/manual scribble/eval "guide-utils.rkt") @(require scribble/manual scribble/eval "guide-utils.rkt"
(for-label rackunit))
@(define cake-eval (make-base-eval)) @(define cake-eval (make-base-eval))
@ -153,9 +154,21 @@ independently. Furthermore, if @filepath{park.rkt} is compiled to a
bytecode file (via @exec{raco make}), then the code for bytecode file (via @exec{raco make}), then the code for
@filepath{park.rkt} or the code for @racket[zoo] can be loaded independently. @filepath{park.rkt} or the code for @racket[zoo] can be loaded independently.
A @racket[module*] form is similar to a nested @racket[module] form, Submodules can be nested within submodules, and a submodule can be
but @racket[module*] inverts the possibilities for reference between referenced directly by a module other than its enclosing module by
the submodule and enclosing module: using a @racket[submod] path as described in
@seclink["module-paths"]{a later section}.
A @racket[module*] form is similar to a nested @racket[module] form:
@specform[
(module* name-id initial-module-path-or-#f
decl ...)
]
The @racket[module*] form differs from @racket[module] in that it
inverts the possibilities for reference between the submodule and
enclosing module:
@itemlist[ @itemlist[
@ -166,23 +179,56 @@ the submodule and enclosing module:
@item{A submodule declared with @racket[module*] can @racket[require] @item{A submodule declared with @racket[module*] can @racket[require]
its enclosing module, but the enclosing module cannot its enclosing module, but the enclosing module cannot
@racket[require] the submodule. In addition, a @racket[module*] @racket[require] the submodule.}
form can specify @racket[#f] as its
@racket[_initial-module-path], in which case the submodule sees
all of the enclosing module's bindings---including bindings
that are not exported via @racket[provide].}
] ]
As an example of @racket[module*], the following variant of In addition, a @racket[module*] form can specify @racket[#f] in place of an
@filepath{cake.rkt} includes a @racket[main] submodule that calls @racket[_initial-module-path], in which case the submodule sees all of
@racket[print-cake]: the enclosing module's bindings---including bindings that are not
exported via @racket[provide].
One use of submodule declared with @racket[module*] and @racket[#f] is
to export additional bindings through a submodule that are not
normally exported from the module:
@racketmod[ @racketmod[
#:file "cake.rkt" #:file "cake.rkt"
racket racket
(provide print-cake) (provide print-cacke)
(define (print-cake n)
(show " ~a " n #\.)
(show " .-~a-. " n #\|)
(show " | ~a | " n #\space)
(show "---~a---" n #\-))
(define (show fmt n ch)
(printf fmt (make-string n ch))
(newline))
(module* extras #f
(provide show))
]
In this revised @filepath{cake.rkt} module, @racket[show] is not
imported by a module that uses @racket[(require "cake.rkt")], since
most clients of @filepath{cake.rkt} will not want the extra function. A
module can require the @racket[extra] @tech{submodule}
(using the @racket[submod] form described in
@seclink["module-paths"]{a later section}) to access the otherwise
hidden @racket[show] function.
@; ----------------------------------------------------------------------
@section[#:tag "main-and-test"]{Main and Test Submodules}
The following variant of @filepath{cake.rkt} includes a @racket[main]
submodule that calls @racket[print-cake]:
@racketmod[
#:file "cake.rkt"
racket
(define (print-cake n) (define (print-cake n)
(show " ~a " n #\.) (show " ~a " n #\.)
@ -198,31 +244,100 @@ racket
(print-cake 10)) (print-cake 10))
] ]
Running a module does not run its @racket[module*]-defined submodules, Running a module does not run its @racket[module*]-defined
since the enclosing module cannot directly reference submodules. Nevertheless, running the above module via @exec{racket}
@racket[module*]-defined submodules. Nevertheless, running the above or DrRacket prints a cake with 10 candles, because the @racket[main]
module via @exec{racket} or DrRacket prints a cake with 10 candles, @tech{submodule} is a special case.
because the @racket[main] submodule} is a special case.
When a module is provided as a program name to the @exec{racket} When a module is provided as a program name to the @exec{racket}
executable or run directly within DrRacket, if the module has a executable or run directly within DrRacket, if the module has a
@as-index{@racket[main] submodule}, the @racket[main] submodule is run after its @as-index{@racket[main] submodule}, the @racket[main] submodule is run
enclosing module. Declaring a @racket[main] submodule is often a after its enclosing module. Declaring a @racket[main] submodule
useful describe tests or other extra actions to be performed when a thus specifies extra actions to be performed when a module is run directly,
module is run directly instead of @racket[required] as a library instead of @racket[required] as a library within a larger program.
within a larger program.
A @racket[main] submodule does not have to be declared with A @racket[main] submodule does not have to be declared with
@racket[module*]. If the @racket[main] module does not need to use @racket[module*]. If the @racket[main] module does not need to use
bindings from its enclosing module, it can be declared with bindings from its enclosing module, it can be declared with
@racket[module]. A @racket[main] submodule typically uses the @racket[module]. More commonly, @racket[main] is declared using a
bindings of its enclosing module, however, so @racket[main] is usually third submodule form, @racket[module+]:
declared with @racket[module*].
Submodules can be nested within submodules, and a submodule can be @specform[
referenced directly by a module other than its enclosing module by (module+ name-id
using a @racket[submod] path as described in the decl ...)
@seclink["module-paths"]{next section}. ]
A submodule declared with @racket[module+] is like one declared with
@racket[module*] using @racket[#f] as its
@racket[_initial-module-path] (i.e., there's no
@racket[_initial-module-path] for @racket[module+]). In addition,
multiple @racket[module+] forms can specify the same submodule name,
in which case the bodies of the @racket[module+] forms are combined to
form a single submodule.
The splicing behavior of @racket[module+] is particularly useful for
defining a @racket[test] submodule, which can be conveniently run
using @exec{raco test} in much the same way that @racket[main] is
conveniently run with @exec{racket}. For example, the following
@filepath{physics.rkt} module exports @racket[drop] and
@racket[to-energy] functions, and it defines a @racket[test] module to
hold unit tests:
@racketmod[
#:file "physics.rkt"
racket
(module+ test
(require rackunit)
(define ε 1e-10))
(provide drop
to-energy)
(define (drop t)
(* 1/2 9.8 t t))
(module+ test
(check-= (drop 0) 0 ε)
(check-= (drop 10) 490 ε))
(define (to-energy m)
(* m (expt 299792458.0 2)))
(module+ test
(check-= (to-energy 0) 0 ε)
(check-= (to-energy 1) 9e+16 1e+15))
]
This module is equivalent to using @racket[module*]:
@racketmod[
#:file "physics.rkt"
racket
(provide drop
to-energy)
(define (drop t)
(* 1/2 #e9.8 t t))
(define (to-energy m)
(* m (expt 299792458 2)))
(module* test #f
(require rackunit)
(define ε 1e-10)
(check-= (drop 0) 0 ε)
(check-= (drop 10) 490 ε)
(check-= (to-energy 0) 0 ε)
(check-= (to-energy 1) 9e+16 1e+15))
]
Using @racket[module+] instead of @racket[module*] allows tests to be
interleaved with function definitions.
The splicing behavior of @racket[module+] is also sometimes helpful
for a @racket[main] module. In any case, @racket[(module+ main ....)]
is preferred as more readable than @racket[(module* main #f ....)].
@; ---------------------------------------------------------------------- @; ----------------------------------------------------------------------

View File

@ -7,15 +7,19 @@
@title[#:tag "test"]{@exec{raco test}: Run tests} @title[#:tag "test"]{@exec{raco test}: Run tests}
The @exec{raco test} command requires and runs the @racket['test] The @exec{raco test} command requires and runs the @racket[test]
submodules associated with paths given on the command line. When a submodule (if any) associated with each path given on the command line. When a
path refers to a directory, the tool recursively discovers all path refers to a directory, the tool recursively discovers all
internal files that end in @filepath{.rkt} and inspects them as well. files that end in @filepath{.rkt} within the directory and runs their
@racket[test] submodules.
The @exec{raco test} command accepts a few flags: The @exec{raco test} command accepts a few flags:
@itemize[ @itemize[
@item{@DFlag{s} @nonterm{id} or @DFlag{submodule} @nonterm{id}--- Requires the submodule @nonterm{id} rather than @racket['test].} @item{@Flag{s} @nonterm{name} or @DFlag{submodule} @nonterm{name}
--- Requires the submodule @nonterm{name} rather than @racket[test].}
@item{@DFlag{r} or @DFlag{run-if-absent}--- Requires the default module if the given submodule is not present in a file.} @item{@Flag{r} or @DFlag{run-if-absent}
--- Requires the top-level module of a file if the relevant submodule is not
present.}
] ]

View File

@ -24,11 +24,16 @@ to another module.
Returns @racket[#f] if @racket[v] is a @tech{resolved module path}, Returns @racket[#f] if @racket[v] is a @tech{resolved module path},
@racket[#f] otherwise.} @racket[#f] otherwise.}
@defproc[(make-resolved-module-path [path (or/c symbol? (and/c path? complete-path?))]) @defproc[(make-resolved-module-path [path (or/c symbol?
(and/c path? complete-path?)
(cons/c (or/c symbol?
(and/c path? complete-path?))
(listof symbol?)))])
resolved-module-path?]{ resolved-module-path?]{
Returns a @tech{resolved module path} that encapsulates @racket[path]. Returns a @tech{resolved module path} that encapsulates @racket[path],
If @racket[path] is not a symbol, it normally should be where a list @racket[path] corresponds to a @tech{submodule} path.
If @racket[path] is a path or starts with a path, the path normally should be
@tech{cleanse}d (see @racket[cleanse-path]) and simplified (see @tech{cleanse}d (see @racket[cleanse-path]) and simplified (see
@racket[simplify-path]). @racket[simplify-path]).
@ -38,9 +43,14 @@ A @tech{resolved module path} is interned. That is, if two
@racket[eq?].} @racket[eq?].}
@defproc[(resolved-module-path-name [module-path resolved-module-path?]) @defproc[(resolved-module-path-name [module-path resolved-module-path?])
(or/c path? symbol?)]{ (or/c symbol?
(and/c path? complete-path?)
(cons/c (or/c symbol?
(and/c path? complete-path?))
(listof symbol?)))]{
Returns the path or symbol encapsulated by a @tech{resolved module path}.} Returns the path or symbol encapsulated by a @tech{resolved module path}.
A list result corresponds to a @tech{submodule} path.}
@defproc[(module-path? [v any/c]) boolean?]{ @defproc[(module-path? [v any/c]) boolean?]{

View File

@ -290,6 +290,8 @@ See also @secref["module-eval-model"] and @secref["mod-parse"].
@defform*[((module* id module-path form ...) @defform*[((module* id module-path form ...)
(module* id #f form ...))]{ (module* id #f form ...))]{
@guideintro["submodules"]{@racket[module*]}
Like @racket[module], but only for declaring a @tech{submodule} within Like @racket[module], but only for declaring a @tech{submodule} within
a module, and for submodules that may @racket[require] the enclosing module. a module, and for submodules that may @racket[require] the enclosing module.
@ -306,6 +308,8 @@ have no effect on the submodule.}
@defform[(module+ id form ...)]{ @defform[(module+ id form ...)]{
@guideintro["main-and-test"]{@racket[module+]}
Declares and/or adds to a @tech{submodule} named @racket[id]. Declares and/or adds to a @tech{submodule} named @racket[id].
Each addition for @racket[id] is combined in order to form the entire Each addition for @racket[id] is combined in order to form the entire