diff --git a/collects/scribblings/guide/contracts-gotchas.scrbl b/collects/scribblings/guide/contracts-gotchas.scrbl index 99d4203628..3eb8e58464 100644 --- a/collects/scribblings/guide/contracts-gotchas.scrbl +++ b/collects/scribblings/guide/contracts-gotchas.scrbl @@ -1,12 +1,99 @@ #lang scribble/doc -@(require scribble/manual +@(require scribble/manual scribble/eval + scheme/sandbox "guide-utils.ss" "contracts-utils.ss" (for-label scheme/contract)) @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]} The contract library assumes that variables exported via @@ -50,34 +137,5 @@ scheme [get-x (-> integer?)]) ] -This is a bug we hope to address in a future release. -@;{ -@question{Contracts and @scheme[eq?]} +Moral: This is a bug we hope to address in a future release. -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?))]) -] - - -}