From 876bc6f02b05c922a853febc927e76e5dae6daa0 Mon Sep 17 00:00:00 2001 From: Matthew Flatt Date: Thu, 22 Mar 2012 14:46:05 -0600 Subject: [PATCH] doc corrections and improvements related to submodules In particular, add `module+' to the Guide. --- collects/compiler/commands/test.rkt | 37 ++-- .../scribblings/guide/module-syntax.scrbl | 175 +++++++++++++++--- collects/scribblings/raco/test.scrbl | 14 +- .../reference/module-reflect.scrbl | 20 +- collects/scribblings/reference/syntax.scrbl | 4 + 5 files changed, 191 insertions(+), 59 deletions(-) diff --git a/collects/compiler/commands/test.rkt b/collects/compiler/commands/test.rkt index 14b728d24e..d0d6c33df6 100644 --- a/collects/compiler/commands/test.rkt +++ b/collects/compiler/commands/test.rkt @@ -7,38 +7,37 @@ (define submodule 'test) (define run-anyways? #f) -(define do-test - (match-lambda - [(? string? s) - (do-test (string->path s))] - [(? path? p) - (define ps (path->string p)) - (cond +(define (do-test e [check-suffix? #f]) + (match e + [(? string? s) + (do-test (string->path s))] + [(? path? p) + (cond [(directory-exists? p) (for-each (λ (dp) - (do-test (build-path p dp))) + (do-test (build-path p dp) #t)) (directory-list p))] [(and (file-exists? p) - (regexp-match #rx"\\.rkt$" ps)) - (define fmod `(file ,ps)) - (define mod `(submod ,fmod ,submodule)) + (or (not check-suffix?) + (regexp-match #rx#"\\.rkt$" (path->bytes p)))) + (define mod `(submod ,p ,submodule)) (cond [(module-declared? mod #t) (dynamic-require mod #f)] - [(and run-anyways? (module-declared? fmod #t)) - (dynamic-require fmod #f)])] + [(and run-anyways? (module-declared? p #t)) + (dynamic-require p #f)])] [(not (file-exists? p)) (error 'test "Given path ~e does not exist" p)])])) (command-line #:program (short-program+command-name) #:once-each - [("--submodule" "-s") submodule-str - "Determines which submodule to load" - (set! submodule (string->symbol submodule-str))] + [("--submodule" "-s") name + "Runs submodule (defaults to `test')" + (set! submodule (string->symbol name))] [("--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)] - #:args files+directories - (for-each do-test files+directories)) + #:args file-or-directory + (for-each do-test file-or-directory)) diff --git a/collects/scribblings/guide/module-syntax.scrbl b/collects/scribblings/guide/module-syntax.scrbl index 49ae3ba8f6..16f61dfac0 100644 --- a/collects/scribblings/guide/module-syntax.scrbl +++ b/collects/scribblings/guide/module-syntax.scrbl @@ -1,5 +1,6 @@ #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)) @@ -153,9 +154,21 @@ independently. Furthermore, if @filepath{park.rkt} is compiled to a bytecode file (via @exec{raco make}), then the code for @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, -but @racket[module*] inverts the possibilities for reference between -the submodule and enclosing module: +Submodules can be nested within submodules, and a submodule can be +referenced directly by a module other than its enclosing module by +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[ @@ -166,23 +179,56 @@ the submodule and enclosing module: @item{A submodule declared with @racket[module*] can @racket[require] its enclosing module, but the enclosing module cannot - @racket[require] the submodule. In addition, a @racket[module*] - 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].} + @racket[require] the submodule.} ] -As an example of @racket[module*], the following variant of -@filepath{cake.rkt} includes a @racket[main] submodule that calls -@racket[print-cake]: +In addition, a @racket[module*] form can specify @racket[#f] in place of an +@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]. + +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[ #:file "cake.rkt" 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) (show " ~a " n #\.) @@ -198,31 +244,100 @@ racket (print-cake 10)) ] -Running a module does not run its @racket[module*]-defined submodules, -since the enclosing module cannot directly reference -@racket[module*]-defined submodules. Nevertheless, running the above -module via @exec{racket} or DrRacket prints a cake with 10 candles, -because the @racket[main] submodule} is a special case. +Running a module does not run its @racket[module*]-defined +submodules. Nevertheless, running the above module via @exec{racket} +or DrRacket prints a cake with 10 candles, because the @racket[main] +@tech{submodule} is a special case. When a module is provided as a program name to the @exec{racket} executable or run directly within DrRacket, if the module has a -@as-index{@racket[main] submodule}, the @racket[main] submodule is run after its -enclosing module. Declaring a @racket[main] submodule is often a -useful describe tests or other extra actions to be performed when a -module is run directly instead of @racket[required] as a library -within a larger program. +@as-index{@racket[main] submodule}, the @racket[main] submodule is run +after its enclosing module. Declaring a @racket[main] submodule +thus specifies extra actions to be performed when a module is run directly, +instead of @racket[required] as a library within a larger program. A @racket[main] submodule does not have to be declared with @racket[module*]. If the @racket[main] module does not need to use bindings from its enclosing module, it can be declared with -@racket[module]. A @racket[main] submodule typically uses the -bindings of its enclosing module, however, so @racket[main] is usually -declared with @racket[module*]. +@racket[module]. More commonly, @racket[main] is declared using a +third submodule form, @racket[module+]: -Submodules can be nested within submodules, and a submodule can be -referenced directly by a module other than its enclosing module by -using a @racket[submod] path as described in the -@seclink["module-paths"]{next section}. +@specform[ +(module+ name-id + decl ...) +] + +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 ....)]. @; ---------------------------------------------------------------------- diff --git a/collects/scribblings/raco/test.scrbl b/collects/scribblings/raco/test.scrbl index 5c988d7b0c..6934b5e32e 100644 --- a/collects/scribblings/raco/test.scrbl +++ b/collects/scribblings/raco/test.scrbl @@ -7,15 +7,19 @@ @title[#:tag "test"]{@exec{raco test}: Run tests} -The @exec{raco test} command requires and runs the @racket['test] -submodules associated with paths given on the command line. When a +The @exec{raco test} command requires and runs the @racket[test] +submodule (if any) associated with each path given on the command line. When a 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: @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.} ] diff --git a/collects/scribblings/reference/module-reflect.scrbl b/collects/scribblings/reference/module-reflect.scrbl index 6d8a96844b..cd848be492 100644 --- a/collects/scribblings/reference/module-reflect.scrbl +++ b/collects/scribblings/reference/module-reflect.scrbl @@ -24,11 +24,16 @@ to another module. Returns @racket[#f] if @racket[v] is a @tech{resolved module path}, @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?]{ -Returns a @tech{resolved module path} that encapsulates @racket[path]. -If @racket[path] is not a symbol, it normally should be +Returns a @tech{resolved module path} that encapsulates @racket[path], +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 @racket[simplify-path]). @@ -38,9 +43,14 @@ A @tech{resolved module path} is interned. That is, if two @racket[eq?].} @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?]{ diff --git a/collects/scribblings/reference/syntax.scrbl b/collects/scribblings/reference/syntax.scrbl index 9c1ab044ed..945a26708d 100644 --- a/collects/scribblings/reference/syntax.scrbl +++ b/collects/scribblings/reference/syntax.scrbl @@ -290,6 +290,8 @@ See also @secref["module-eval-model"] and @secref["mod-parse"]. @defform*[((module* id module-path form ...) (module* id #f form ...))]{ +@guideintro["submodules"]{@racket[module*]} + Like @racket[module], but only for declaring a @tech{submodule} within 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 ...)]{ +@guideintro["main-and-test"]{@racket[module+]} + Declares and/or adds to a @tech{submodule} named @racket[id]. Each addition for @racket[id] is combined in order to form the entire