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.
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[
@item{a submodule named @tt{no-contract}, which defines the
functionality and exports some of it to the surrounding module}
@item{a @racket[provide] specification with a @racket[contract-out] clause
in the outer module that re-exports the desired pieces of functionality.}
@item{the @tt{no-contract} submodule @racket[provide]s the functionality @emph{without} contracts,}
@item{the main module @racket[provide]s the functionality @emph{with} contracts.}
]
This section explains three strategies for three different situations and
levels of implementation complexity.
@ -160,9 +158,9 @@ levels of implementation complexity.
explains the basics of our understanding of ``safety'' and link to it.}
@;
@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
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
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,
@ -216,7 +214,8 @@ the @racket[#:unprotected-submodule] functionality of @racket[contract-out].
(provide
(contract-out
(code:hilite #:unprotected-submodule) (code:hilite no-contract)
(code:hilite #:unprotected-submodule)
(code:hilite no-contract)
(human strategy/c)
(ai strategy/c)))
@ -233,8 +232,8 @@ the @racket[#:unprotected-submodule] functionality of @racket[contract-out].
((general 'tra) x))))
]
The example labeled @tt{good} illustrates what the module might look
like originally. Every exported function comes with a contract, and the
The module called @tt{good} illustrates what the code might look
like originally. Every exported functions come with contracts, and the
definitions of these functions can be found below the @racket[provide]
specification in the module body. The @tt{fast} module on the right
requests the creation of a submodule named @tt{no-contract}, which exports
@ -250,12 +249,12 @@ straightforward:
@tt{needs-goodness}
racket
(require coll/fast)
(require "fast.rkt")
human
;; comes with contracts
;; as if we had required
;; coll/good
;; "good.rkt" itself
(define state1 0)
(define state2
@ -269,7 +268,7 @@ straightforward:
(require
(submod
coll/fast
"fast.rkt"
no-contract))
human
@ -281,16 +280,16 @@ straightforward:
(define action*
(map human state*))))
]
Both modules @racket[require] the @tt{fast} module, but the left one goes
through the contracted @racket[provide] while and the right one uses the
@tt{no-contract} submodule. Hence the left module imports, say,
@racket[human] with contracts; the right one imports the same function
without contract and thus doesn't have to pay the performance penalty.
Both modules @racket[require] the @tt{fast} module, but @tt{needs-goodness}
on the left goes through the contracted @racket[provide] while
@tt{needs-speed} on the right uses the @tt{no-contract} submodule. Tchnically,
the left module imports @racket[human] with contracts; the right one
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
have installed @tt{fast} in some collection @tt{coll} appropriately---the
left one raises an contract error while the right one binds
@racket[action*] to
saved them with the correct names in some folder---the left one raises a
contract error while the right one binds @racket[action*] to
@;%
@(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
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:
@;%
@(begin
@ -335,10 +334,10 @@ contracts, requiring the @tt{no-contract} still raises a contract error:
(require (submod "." server no-contract))
))
@;%
@bold{Explanation} The @racket[require] runs the body of the main module,
and doing so checks the first-order properties of the exported values---and
because @racket[human] is not a function, this evaluation raises a contract
error.
@bold{Explanation} The @tt{no-contract} submodule depends on the main
module, so the require runs the body of the main module, and doing so
checks the first-order properties of the exported values. Because
@racket[human] is not a function, this evaluation raises a contract error.
The @emph{second} way to create a @tt{no-contract} submodule requires
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
#reader scribble/comment-reader
(racketmod0 #:file
@tt{good}
@tt{good2}
racket
(define state? zero?)
@ -377,7 +376,7 @@ above, with the right one derived manually from the one on the left:
@(begin
#reader scribble/comment-reader
(racketmod0 #:file
@tt{fast}
@tt{fast2}
racket
(define state? zero?)
@ -410,20 +409,25 @@ above, with the right one derived manually from the one on the left:
(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
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,
making the identifiers available in the outer scope so that the contracted
@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
useful when the presence of contracts prevents a module from being used in
a context where contracts aren't available at all. One example is
@rkt/base[]; another is the contracts library itself. Again, you may wish
you had the same library without contracts. For these cases, we recommend a
file-based strategy one. Assuming the library is located at @tt{a/b/c}, we
recommend
useful when contracts prevents a module from being used in a context where
contracts aren't available at all---neither at compile nor at run time. One
example is @rkt/base[]; another is the contracts library itself. Again, you
may wish you had the same library without contracts. For these cases, we
recommend a file-based strategy one. Assuming the library is located at
@tt{a/b/c}, we recommend
@itemlist[#:style 'ordered