some more criticisms from Robby and clarifications
This commit is contained in:
parent
ceef969d48
commit
7a6d567daf
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user