svn: r12063

This commit is contained in:
Robby Findler 2008-10-19 03:03:45 +00:00
parent 881d884b67
commit 860bbfe0a0

View File

@ -1,12 +1,99 @@
#lang scribble/doc #lang scribble/doc
@(require scribble/manual @(require scribble/manual
scribble/eval scribble/eval
scheme/sandbox
"guide-utils.ss" "guide-utils.ss"
"contracts-utils.ss" "contracts-utils.ss"
(for-label scheme/contract)) (for-label scheme/contract))
@title[#:tag "contracts-gotchas"]{Gotchas} @title[#:tag "contracts-gotchas"]{Gotchas}
@ctc-section{Contracts and @scheme[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 PLT Scheme contracts, with one exception: @scheme[eq?].
The @scheme[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 PLT Scheme is implemented (and how contracts are
implemented).
Contracts interact poorly with @scheme[eq?] because function
contract checking is implemented internally as wrapper
functions. For example, consider this module:
@schememod[
scheme
(define (make-adder x)
(if (= 1 x)
add1
(lambda (y) (+ x 1))))
(provide/contract [make-adder (-> number? (-> number? number?))])
]
It exports the @scheme[make-adder] function that is the usual curried
addition function, except that it returns Scheme's @scheme[add1] when
its input is @scheme[1].
You might expect that
@schemeblock[
(eq? (make-adder 1)
(make-adder 1))
]
would return @scheme[#t], but it does not. If the contract were
changed to @scheme[any/c] (or even @scheme[(-> number? any/c)]), then
the @scheme[eq?] call would return @scheme[#t].
Moral: do not use @scheme[eq?] on values that have contracts.
@ctc-section{Defining recursive contracts}
When defining a self-referential contract, it is natural to use
@scheme[define]. For example, one might try to write a contract on
streams like this:
@interaction[
#:eval
(parameterize ([sandbox-security-guard (current-security-guard)]
[sandbox-output 'string]
[sandbox-error-output 'string]
[sandbox-eval-limits #f]
[sandbox-make-inspector current-inspector])
(make-evaluator '(begin (require scheme))))
(define stream/c
(promise/c
(or/c
null?
(cons/c number? stream/c))))
]
Unfortunately, this does not work because the value of
@scheme[stream/c] is needed before it is defined. Put another way, all
of the combinators evaluate their arguments eagerly, even thought the
values that they accept do not.
Instead, use
@schemeblock[
(define stream/c
(promise/c
(or/c
null?
(cons/c 1
(recursive-contract stream/c)))))
]
The use of @scheme[recursive-contract] delays the evaluation of the
identifier @scheme[stream/c] until after the contract is first
checked, long enough to ensure that @scheme[stream/c] is defined.
See also @ctc-link["lazy-contracts"].
@ctc-section{Using @scheme[set!] to Assign to Variables Provided via @scheme[provide/contract]} @ctc-section{Using @scheme[set!] to Assign to Variables Provided via @scheme[provide/contract]}
The contract library assumes that variables exported via The contract library assumes that variables exported via
@ -50,34 +137,5 @@ scheme
[get-x (-> integer?)]) [get-x (-> integer?)])
] ]
This is a bug we hope to address in a future release. Moral: This is a bug we hope to address in a future release.
@;{
@question{Contracts and @scheme[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 PLT Scheme contracts, with one exception: @scheme[eq?].
The @scheme[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 PLT Scheme is implemented (and how contracts are
implemented).
Contracts interact poorly with @scheme[eq?] because function
contract checking is implemented internally as wrapper
functions. For example, consider this module:
@schememod[
scheme
(define (make-adder ))
(provide make-adder)
(provide/contract [make-adder (-> number? (-> number? number?))])
]
}