some more criticisms from Robby and clarifications

This commit is contained in:
Matthias Felleisen 2019-05-22 12:14:18 -04:00
parent ceef969d48
commit 7a6d567daf

View File

@ -144,14 +144,12 @@ expands to many nested definitions and expressions every time it is used.
Adding contracts to a library is good. Adding contracts to a library is good.
On some occasions, contracts impose a significant performance penalty. On some occasions, contracts impose a significant performance penalty.
For such cases, we recommend organizing the module into two parts: For such cases, we recommend organizing the module into a main module as
usual and a submodule called @tt{no-contract} so that
@itemlist[ @itemlist[
@item{a submodule named @tt{no-contract}, which defines the @item{the @tt{no-contract} submodule @racket[provide]s the functionality @emph{without} contracts,}
functionality and exports some of it to the surrounding module} @item{the main module @racket[provide]s the functionality @emph{with} contracts.}
@item{a @racket[provide] specification with a @racket[contract-out] clause
in the outer module that re-exports the desired pieces of functionality.}
] ]
This section explains three strategies for three different situations and This section explains three strategies for three different situations and
levels of implementation complexity. levels of implementation complexity.
@ -160,9 +158,9 @@ levels of implementation complexity.
explains the basics of our understanding of ``safety'' and link to it.} explains the basics of our understanding of ``safety'' and link to it.}
@; @;
@bold{Warning} Splitting contracted functionality into two modules in @bold{Warning} Splitting contracted functionality into two modules in
this way renders the code in the @tt{no-contract} @bold{unsafe}. The this way renders the code in the @tt{no-contract} module @bold{unsafe}. The
creator of the original code might have assumed certain constraints on some creator of the original code might have assumed certain constraints on some
function's arguments, and the contracts checked these constraints. While functions' arguments, and the contracts checked these constraints. While
the documentation of the @tt{no-contract} submodule is likely to state the documentation of the @tt{no-contract} submodule is likely to state
these constraints, it is left to the client to check them. If the client these constraints, it is left to the client to check them. If the client
code doesn't check the constraints and the arguments don't satisfy them, code doesn't check the constraints and the arguments don't satisfy them,
@ -216,7 +214,8 @@ the @racket[#:unprotected-submodule] functionality of @racket[contract-out].
(provide (provide
(contract-out (contract-out
(code:hilite #:unprotected-submodule) (code:hilite no-contract) (code:hilite #:unprotected-submodule)
(code:hilite no-contract)
(human strategy/c) (human strategy/c)
(ai strategy/c))) (ai strategy/c)))
@ -233,8 +232,8 @@ the @racket[#:unprotected-submodule] functionality of @racket[contract-out].
((general 'tra) x)))) ((general 'tra) x))))
] ]
The example labeled @tt{good} illustrates what the module might look The module called @tt{good} illustrates what the code might look
like originally. Every exported function comes with a contract, and the like originally. Every exported functions come with contracts, and the
definitions of these functions can be found below the @racket[provide] definitions of these functions can be found below the @racket[provide]
specification in the module body. The @tt{fast} module on the right specification in the module body. The @tt{fast} module on the right
requests the creation of a submodule named @tt{no-contract}, which exports requests the creation of a submodule named @tt{no-contract}, which exports
@ -250,12 +249,12 @@ straightforward:
@tt{needs-goodness} @tt{needs-goodness}
racket racket
(require coll/fast) (require "fast.rkt")
human human
;; comes with contracts ;; comes with contracts
;; as if we had required ;; as if we had required
;; coll/good ;; "good.rkt" itself
(define state1 0) (define state1 0)
(define state2 (define state2
@ -269,7 +268,7 @@ straightforward:
(require (require
(submod (submod
coll/fast "fast.rkt"
no-contract)) no-contract))
human human
@ -281,16 +280,16 @@ straightforward:
(define action* (define action*
(map human state*)))) (map human state*))))
] ]
Both modules @racket[require] the @tt{fast} module, but the left one goes Both modules @racket[require] the @tt{fast} module, but @tt{needs-goodness}
through the contracted @racket[provide] while and the right one uses the on the left goes through the contracted @racket[provide] while
@tt{no-contract} submodule. Hence the left module imports, say, @tt{needs-speed} on the right uses the @tt{no-contract} submodule. Tchnically,
@racket[human] with contracts; the right one imports the same function the left module imports @racket[human] with contracts; the right one
without contract and thus doesn't have to pay the performance penalty. imports the same function without contract and thus doesn't have to pay the
performance penalty.
Notice, however, that when you run these two client modules---assuming you Notice, however, that when you run these two client modules---assuming you
have installed @tt{fast} in some collection @tt{coll} appropriately---the saved them with the correct names in some folder---the left one raises a
left one raises an contract error while the right one binds contract error while the right one binds @racket[action*] to
@racket[action*] to
@;% @;%
@(begin @(begin
@ -301,7 +300,7 @@ left one raises an contract error while the right one binds
@;% @;%
The @tt{no-contract} submodule generated by this first, easy approach The @tt{no-contract} submodule generated by this first, easy approach
depends on @racketmodname[#, 'racket/contract] at both compile and run time. retains the dependency on @racketmodname[#, 'racket/contract] at both compile and run time.
Here is a variant of the above module that demonstrates this point: Here is a variant of the above module that demonstrates this point:
@;% @;%
@(begin @(begin
@ -335,10 +334,10 @@ contracts, requiring the @tt{no-contract} still raises a contract error:
(require (submod "." server no-contract)) (require (submod "." server no-contract))
)) ))
@;% @;%
@bold{Explanation} The @racket[require] runs the body of the main module, @bold{Explanation} The @tt{no-contract} submodule depends on the main
and doing so checks the first-order properties of the exported values---and module, so the require runs the body of the main module, and doing so
because @racket[human] is not a function, this evaluation raises a contract checks the first-order properties of the exported values. Because
error. @racket[human] is not a function, this evaluation raises a contract error.
The @emph{second} way to create a @tt{no-contract} submodule requires The @emph{second} way to create a @tt{no-contract} submodule requires
systematic work from the developer and eliminates the run-time dependency systematic work from the developer and eliminates the run-time dependency
@ -349,7 +348,7 @@ above, with the right one derived manually from the one on the left:
@(begin @(begin
#reader scribble/comment-reader #reader scribble/comment-reader
(racketmod0 #:file (racketmod0 #:file
@tt{good} @tt{good2}
racket racket
(define state? zero?) (define state? zero?)
@ -377,7 +376,7 @@ above, with the right one derived manually from the one on the left:
@(begin @(begin
#reader scribble/comment-reader #reader scribble/comment-reader
(racketmod0 #:file (racketmod0 #:file
@tt{fast} @tt{fast2}
racket racket
(define state? zero?) (define state? zero?)
@ -410,20 +409,25 @@ above, with the right one derived manually from the one on the left:
(require 'no-contract) (require 'no-contract)
)) ))
] ]
Here the @tt{fast} module on the right encapsulates the The @tt{fast2} module on the right encapsulates the
definitions in a submodule called @tt{no-contract}; the @racket[provide] in definitions in a submodule called @tt{no-contract}; the @racket[provide] in
this submodule exports the exact same identifiers as the @tt{good} module this submodule exports the exact same identifiers as the @tt{good2} module
on the left. The main module @racket[require]s the submodule immediately, on the left. The main module @racket[require]s the submodule immediately,
making the identifiers available in the outer scope so that the contracted making the identifiers available in the outer scope so that the contracted
@code{provide} can re-export them. @code{provide} can re-export them.
While this second way of creating a @tt{no-contract} submodule eliminates
the run-time dependency on @racketmodname[#, 'racket/contract], its
compilation---as a part of the outer module---still depends on this
library, which is problematic in a few remaining situations.
The @emph{third} and last way to create a @tt{no-contract} submodule is The @emph{third} and last way to create a @tt{no-contract} submodule is
useful when the presence of contracts prevents a module from being used in useful when contracts prevents a module from being used in a context where
a context where contracts aren't available at all. One example is contracts aren't available at all---neither at compile nor at run time. One
@rkt/base[]; another is the contracts library itself. Again, you may wish example is @rkt/base[]; another is the contracts library itself. Again, you
you had the same library without contracts. For these cases, we recommend a may wish you had the same library without contracts. For these cases, we
file-based strategy one. Assuming the library is located at @tt{a/b/c}, we recommend a file-based strategy one. Assuming the library is located at
recommend @tt{a/b/c}, we recommend
@itemlist[#:style 'ordered @itemlist[#:style 'ordered