racket/pkgs/racket-doc/scribblings/guide/contracts/gotchas.scrbl
Matthew Flatt 2d4f3e2ac9 remove the "racket-pkgs" directory layer
The layer is now redundant, since everything left in "pkgs" is in the
"racket-pkgs" category.
2014-12-08 05:22:59 -07:00

213 lines
6.6 KiB
Racket

#lang scribble/doc
@(require scribble/manual scribble/eval racket/sandbox
"utils.rkt"
(for-label racket/contract))
@title[#:tag "contracts-gotchas"]{Gotchas}
@ctc-section{Contracts and @racket[eq?]}
As a general rule, adding a contract to a program should
either leave the behavior of the program unchanged, or
should signal a contract violation. And this is almost true
for Racket contracts, with one exception: @racket[eq?].
The @racket[eq?] procedure is designed to be fast and does
not provide much in the way of guarantees, except that if it
returns true, it means that the two values behave
identically in all respects. Internally, this is implemented
as pointer equality at a low-level so it exposes information
about how Racket is implemented (and how contracts are
implemented).
Contracts interact poorly with @racket[eq?] because function
contract checking is implemented internally as wrapper
functions. For example, consider this module:
@racketmod[
racket
(define (make-adder x)
(if (= 1 x)
add1
(lambda (y) (+ x y))))
(provide (contract-out
[make-adder (-> number? (-> number? number?))]))
]
It exports the @racket[make-adder] function that is the usual curried
addition function, except that it returns Racket's @racket[add1] when
its input is @racket[1].
You might expect that
@racketblock[
(eq? (make-adder 1)
(make-adder 1))
]
would return @racket[#t], but it does not. If the contract were
changed to @racket[any/c] (or even @racket[(-> number? any/c)]), then
the @racket[eq?] call would return @racket[#t].
Moral: Do not use @racket[eq?] on values that have contracts.
@ctc-section[#:tag "gotcha-nested"]{Contract boundaries and @racket[define/contract]}
The contract boundaries established by @racket[define/contract], which
creates a nested contract boundary, are 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)
]
One might expect that the function @racket[g] will be blamed
for breaking the terms of its contract with @racket[f].
Blaming @racket[g] would be right if @racket[f] and @racket[g]
were directly establishing contracts with each other.
They aren't, however. Instead, the access between @racket[f]
and @racket[g] is mediated through the top-level of the enclosing
module.
More precisely, @racket[f] and the top-level of the module have
the @racket[(-> integer? integer?)] contract mediating their
interaction; @racket[g] and the top-level have @racket[(-> string?)]
mediating their interaction, but there is no contract directly
between @racket[f] and @racket[g]. This means that the reference to
@racket[f] in the body of @racket[g] is really the top-level
of the module's responsibility, not @racket[g]'s. In other words,
the function @racket[f] has been given to @racket[g] with
no contract between @racket[g] and the top-level and thus
the top-level is blamed.
If we wanted to add a contract between @racket[g] and the
top-level, we can use @racket[define/contract]'s
@racket[#:freevar] declaration and see the expected blame:
@interaction[#:eval e2
(define/contract (f x)
(-> integer? integer?)
x)
(define/contract (g)
(-> string?)
#:freevar f (-> integer? integer?)
(f "not an integer"))
(g)
]
@(close-eval e2)
Moral: if two values with contracts should interact,
put them in separate modules with contracts at
the module boundary or use @racket[#:freevar].
@ctc-section[#:tag "exists-gotcha"]{Exists Contracts and Predicates}
Much like the @racket[eq?] example above, @racket[#:∃] contracts
can change the behavior of a program.
Specifically,
the @racket[null?] predicate (and many other predicates) return @racket[#f]
for @racket[#:∃] contracts, and changing one of those contracts to @racket[any/c]
means that @racket[null?] might now return @racket[#t] instead, resulting in
arbitrarily different behavior depending on how this boolean might flow around
in the program.
@defmodulelang[racket/exists]
To work around the above problem, the
@racketmodname[racket/exists] library behaves just like @racketmodname[racket],
but predicates signal errors when given @racket[#:∃] contracts.
Moral: Do not use predicates on @racket[#:∃] contracts, but if you're not sure, use
@racketmodname[racket/exists] to be safe.
@ctc-section{Defining Recursive Contracts}
When defining a self-referential contract, it is natural to use
@racket[define]. For example, one might try to write a contract on
streams like this:
@(define e (make-base-eval))
@(interaction-eval #:eval e (require racket/contract))
@interaction[
#:eval e
(define stream/c
(promise/c
(or/c null?
(cons/c number? stream/c))))
]
@close-eval[e]
Unfortunately, this does not work because the value of
@racket[stream/c] is needed before it is defined. Put another way, all
of the combinators evaluate their arguments eagerly, even though the
values that they accept do not.
Instead, use
@racketblock[
(define stream/c
(promise/c
(or/c
null?
(cons/c number? (recursive-contract stream/c)))))
]
The use of @racket[recursive-contract] delays the evaluation of the
identifier @racket[stream/c] until after the contract is first
checked, long enough to ensure that @racket[stream/c] is defined.
See also @ctc-link["lazy-contracts"].
@ctc-section{Mixing @racket[set!] and @racket[contract-out]}
The contract library assumes that variables exported via
@racket[contract-out] are not assigned to, but does not enforce
it. Accordingly, if you try to @racket[set!] those variables, you
may be surprised. Consider the following example:
@interaction[
(module server racket
(define (inc-x!) (set! x (+ x 1)))
(define x 0)
(provide (contract-out [inc-x! (-> void?)]
[x integer?])))
(module client racket
(require 'server)
(define (print-latest) (printf "x is ~s\n" x))
(print-latest)
(inc-x!)
(print-latest))
(require 'client)
]
Both calls to @racket[print-latest] print @racket[0], even though the
value of @racket[x] has been incremented (and the change is visible
inside the module @racket[x]).
To work around this, export accessor functions, rather than
exporting the variable directly, like this:
@racketmod[
racket
(define (get-x) x)
(define (inc-x!) (set! x (+ x 1)))
(define x 0)
(provide (contract-out [inc-x! (-> void?)]
[get-x (-> integer?)]))
]
Moral: This is a bug that we will address in a future release.