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:
Asumu Takikawa 2014-02-08 21:27:02 -05:00
parent f26279bfa2
commit d37158bd76
3 changed files with 93 additions and 0 deletions

View File

@ -153,3 +153,39 @@ racket
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.

View File

@ -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[require] statement (where each reference through a
@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"].

View File

@ -100,6 +100,36 @@ is just another way of writing
(-> 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]}