Add initial prose for define/contract in the Guide
This commit adds a few sections that explain some of the subtleties of nested contract boundaries.
This commit is contained in:
parent
f26279bfa2
commit
d37158bd76
|
@ -153,3 +153,39 @@ racket
|
||||||
|
|
||||||
Moral: This is a bug that we will address in a future release.
|
Moral: This is a bug that we will address in a future release.
|
||||||
|
|
||||||
|
@ctc-section[#:tag "gotcha-nested"]{Contract boundaries established by @racket[define/contract]}
|
||||||
|
|
||||||
|
The contract boundaries established by @racket[define/contract], which
|
||||||
|
creates a nested contract boundary, is sometimes unintuitive. This is
|
||||||
|
especially true when multiple functions or other values with contracts
|
||||||
|
interact. For example, consider these two interacting functions:
|
||||||
|
|
||||||
|
@(define e2 (make-base-eval))
|
||||||
|
@(interaction-eval #:eval e2 (require racket/contract))
|
||||||
|
@interaction[#:eval e2
|
||||||
|
(define/contract (f x)
|
||||||
|
(-> integer? integer?)
|
||||||
|
x)
|
||||||
|
(define/contract (g)
|
||||||
|
(-> string?)
|
||||||
|
(f "not an integer"))
|
||||||
|
(g)
|
||||||
|
]
|
||||||
|
@(close-eval e2)
|
||||||
|
|
||||||
|
One may expect that the function @racket[g] will be blamed
|
||||||
|
for breaking the terms of its contract with @racket[f]. Instead, the
|
||||||
|
``top-level'' is blamed.
|
||||||
|
|
||||||
|
The reason that the ``top-level'' is blamed is because there are two
|
||||||
|
contract boundaries in play and none of them are directly between
|
||||||
|
@racket[f] and @racket[g]. Both @racket[f] and @racket[g] establish
|
||||||
|
boundaries between themselves and their context, i.e., the top-level.
|
||||||
|
In order for @racket[f] to be called by @racket[g], the arguments
|
||||||
|
that are supplied by @racket[g] pass through the top-level, which
|
||||||
|
takes responsibility for the values ultimately supplied to @racket[f].
|
||||||
|
Thus, it makes sense that the top-level is blamed.
|
||||||
|
|
||||||
|
Moral: if two values with contracts should interact,
|
||||||
|
consider putting them in separate modules with contracts at
|
||||||
|
the module boundary.
|
|
@ -210,3 +210,30 @@ with the @racket[module+] keyword at the front. The first form after
|
||||||
@racket[module] is the name of the module to be used in a subsequent
|
@racket[module] is the name of the module to be used in a subsequent
|
||||||
@racket[require] statement (where each reference through a
|
@racket[require] statement (where each reference through a
|
||||||
@racket[require] prefixes the name with @racket[".."]).
|
@racket[require] prefixes the name with @racket[".."]).
|
||||||
|
|
||||||
|
@ctc-section[#:tag "intro-nested"]{Experimenting with Nested Contract Boundaries}
|
||||||
|
|
||||||
|
In many cases, it makes sense to attach contracts at module boundaries.
|
||||||
|
It is often convenient, however, to be able to use contracts at
|
||||||
|
a finer granularity than modules. The @racket[define/contract]
|
||||||
|
form enables this kind of use:
|
||||||
|
|
||||||
|
@racketmod[
|
||||||
|
racket
|
||||||
|
|
||||||
|
(define/contract amount
|
||||||
|
(and/c number? positive?)
|
||||||
|
150)
|
||||||
|
|
||||||
|
(+ amount 10)
|
||||||
|
]
|
||||||
|
|
||||||
|
In this example, the @racket[define/contract] form establishes a contract
|
||||||
|
boundary between the definition of @racket[amount] and its surrounding
|
||||||
|
context. In other words, the two parties here are the definition and
|
||||||
|
the module that contains it.
|
||||||
|
|
||||||
|
Forms that create these @emph{nested contract boundaries} can sometimes
|
||||||
|
be subtle to use because they may have unexpected performance implications
|
||||||
|
or blame a party that may seem unintuitive. These subtleties are explained
|
||||||
|
in @secref["simple-nested"] and @ctc-link["gotcha-nested"].
|
||||||
|
|
|
@ -100,6 +100,36 @@ is just another way of writing
|
||||||
(-> number? any)
|
(-> number? any)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@; ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
@section[#:tag "simple-nested"]{Using @racket[define/contract] and @racket[->]}
|
||||||
|
|
||||||
|
The @racket[define/contract] form introduced in @ctc-link["intro-nested"] can
|
||||||
|
also be used to define functions that come with a contract. For example,
|
||||||
|
|
||||||
|
@racketblock[
|
||||||
|
(define/contract (deposit amount)
|
||||||
|
(-> number? any)
|
||||||
|
(code:comment "implementation goes here")
|
||||||
|
....)
|
||||||
|
]
|
||||||
|
|
||||||
|
which defines the @racket[deposit] function with the contract from earlier.
|
||||||
|
Note that this has two potentially important impacts on the use of
|
||||||
|
@racket[deposit]:
|
||||||
|
|
||||||
|
@itemlist[#:style "ordered"
|
||||||
|
@item{Since the contract will always be checked on calls to @racket[deposit],
|
||||||
|
even inside the module in which it is defined, this may increase
|
||||||
|
the number of times the contract is checked. This could lead to
|
||||||
|
a performance degradation. This is especially true if the function
|
||||||
|
is called repeatedly in loops or recursion.}
|
||||||
|
@item{In some situations, a function may be written to accept a more
|
||||||
|
lax set of inputs when called by other code in the same module.
|
||||||
|
For such use cases, the contract boundary established by
|
||||||
|
@racket[define/contract] is too strict.}
|
||||||
|
]
|
||||||
|
|
||||||
@; ----------------------------------------------------------------------
|
@; ----------------------------------------------------------------------
|
||||||
@section{@racket[any] and @racket[any/c]}
|
@section{@racket[any] and @racket[any/c]}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user