svn: r12063
This commit is contained in:
parent
881d884b67
commit
860bbfe0a0
|
@ -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?))])
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user