diff --git a/pkgs/racket-doc/scribblings/guide/contracts/new-combinators.scrbl b/pkgs/racket-doc/scribblings/guide/contracts/new-combinators.scrbl index 7adf52f0ce..49027f5dae 100644 --- a/pkgs/racket-doc/scribblings/guide/contracts/new-combinators.scrbl +++ b/pkgs/racket-doc/scribblings/guide/contracts/new-combinators.scrbl @@ -44,7 +44,7 @@ they are not quite ready for use as contracts, because they do not accommodate blame and do not provide good error messages. In order to accommodate these, contracts do not just use simple projections, but use functions that accept a -@deftech{blame object} encapsulating +@tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{blame object} encapsulating the names of two parties that are the candidates for blame, as well as a record of the source location where the contract was established and the name of the contract. They @@ -109,7 +109,8 @@ arguments in the first two lines of the @racket[int->int-proj] function. The trick here is that, even though the @racket[int->int-proj] function always blames what it sees as positive, we can swap the blame parties by -calling @racket[blame-swap] on the given @tech{blame object}, replacing +calling @racket[blame-swap] on the given +@tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{blame object}, replacing the positive party with the negative party and vice versa. This technique is not merely a cheap trick to get the example to work, @@ -162,7 +163,8 @@ when a contract violation is detected. While these projections are supported by the contract library and can be used to build new contracts, the contract library also supports a different API for projections that can be more -efficient. Specifically, a @deftech{late neg projection} accepts +efficient. Specifically, a +@tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{late neg projection} accepts a blame object without the negative blame information and then returns a function that accepts both the value to be contracted and the name of the negative party, in that order. @@ -196,11 +198,55 @@ to use this API looks like this: '(expected "a procedure of one argument" given: "~e") f))))] The advantage of this style of contract is that the @racket[_blame] -and @racket[_f] arguments can be supplied on the server side of the -contract boundary and the result can be used for every different +argument can be supplied on the server side of the +contract boundary and the result can be used for each different client. With the simpler situation, a new blame object has to be created for each client. +One final problem remains before this contract can be used with the +rest of the contract system. In the function above, +the contract is implemented by creating a wrapper function for +@racket[f], but this wrapper function does not cooperate with +@racket[equal?], nor does it let the runtime system know that there +is a relationship between the result function and @racket[f], the input +function. + +To remedy these two problems, we should use +@tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{chaperones} instead +of just using @racket[λ] to create the wrapper function. Here is the +@racket[int->int-proj] function rewritten to use a +@tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{chaperone}: + +@interaction/no-prompt[#:eval ex-eval +(define (int->int-proj blame) + (define dom-blame (blame-add-context blame + "the argument of" + #:swap? #t)) + (define rng-blame (blame-add-context blame "the range of")) + (define (check-int v to-blame neg-party) + (unless (integer? v) + (raise-blame-error + to-blame #:missing-party neg-party + v + '(expected "an integer" given: "~e") + v))) + (λ (f neg-party) + (if (and (procedure? f) + (procedure-arity-includes? f 1)) + (chaperone-procedure + f + (λ (x) + (check-int x dom-blame neg-party) + (values (λ (ans) + (check-int ans rng-blame neg-party) + ans) + x))) + (raise-blame-error + blame #:missing-party neg-party + f + '(expected "a procedure of one argument" given: "~e") + f))))] + Projections like the ones described above, but suited to other, new kinds of value you might make, can be used with the contract library primitives. Specifically, we can use @@ -470,5 +516,4 @@ to use in this program: (maybe-accepts-a-function sqrt) (maybe-accepts-a-function 123)] - @(close-eval ex-eval) diff --git a/pkgs/racket-doc/scribblings/reference/contracts.scrbl b/pkgs/racket-doc/scribblings/reference/contracts.scrbl index c0f0ced6f4..dac3eceb4c 100644 --- a/pkgs/racket-doc/scribblings/reference/contracts.scrbl +++ b/pkgs/racket-doc/scribblings/reference/contracts.scrbl @@ -9,10 +9,8 @@ (the-eval '(require racket/contract racket/contract/parametric racket/list)) the-eval))) -@(define blame-object - @tech[#:doc '(lib "scribblings/guide/guide.scrbl")]{blame object}) -@(define blame-objects - @tech[#:doc '(lib "scribblings/guide/guide.scrbl")]{blame objects}) +@(define blame-object @tech{blame object}) +@(define blame-objects @tech{blame objects}) @title[#:tag "contracts" #:style 'toc]{Contracts} @@ -2182,8 +2180,9 @@ The default test accepts any value. The predicate should be influenced by the value of @racket[(contract-first-order-okay-to-give-up?)] (see it's documentation for more explanation). -The @racket[late-neg-proj] defines the behavior of applying the contract. If it is -supplied, it accepts a blame object that does not have a value for +The @racket[late-neg-proj] argument defines the behavior of applying + the contract via a @deftech{late neg projection}. If it is + supplied, it accepts a @tech{blame object} that does not have a value for the @racket[blame-negative] field. Then it must return a function that accepts both the value that is getting the contract and the name of the blame party, in that order. The result must either be the value (perhaps suitably wrapped @@ -2360,16 +2359,18 @@ For the costs from checking your new combinator to be included, you should wrap any deferred, higher-order checks with this form. First-order checks are recognized automatically and do not require this form. -If your combinator's projections operate on complete blame objects (i.e., no -missing blame parties), the blame object should be the first argument to this +If your combinator's projections operate on complete @tech{blame objects} (i.e., no +missing blame parties), the @tech{blame object} should be the first argument to this form. Otherwise (e.g., in the case of @racket[_late-neg] projections), a pair -of the blame object and the negative party should be used instead. +of the @tech{blame object} and the negative party should be used instead. @history[#:added "6.4.0.4"] } @subsection{Blame Objects} +This section describes @deftech{blame objects} and operations on them. + @defproc[(blame? [v any/c]) boolean?]{ This predicate recognizes @|blame-objects|. } @@ -2520,7 +2521,7 @@ the other; both are provided for convenience and clarity. @defproc[(blame-add-missing-party [b (and/c blame? blame-missing-party?)] [missing-party any/c]) (and/c blame? (not/c blame-missing-party?))]{ - Produces a new blame object like @racket[b], except that the missing + Produces a new @tech{blame object} like @racket[b], except that the missing party is replaced with @racket[missing-party]. } @@ -2619,8 +2620,8 @@ The value is expected to be the blame record for the contract on the value or a @racket[cons]-pair of a blame record with a missing party and the missing party. The @racket[value-blame] function reassembles the arguments of the pair into a complete blame record using @racket[blame-add-missing-party]. If -the value has one of the properties, but the value is not a blame object -or a pair whose @racket[car] position is a blame object, then @racket[has-blame?] +the value has one of the properties, but the value is not a @tech{blame object} +or a pair whose @racket[car] position is a @tech{blame object}, then @racket[has-blame?] returns @racket[#f] but @racket[value-blame] returns @racket[#f]. } @@ -3080,7 +3081,7 @@ Produces the name used to describe the contract in error messages. the contract checking, mostly used to create a meaningful error message if a contract violation is detected. The resulting function's first argument is the value that should have the contract and its second argument is - a ``missing party'' from the blame object, to be passed to @racket[raise-contract-error]. + a ``missing party'' from the @tech{blame object}, to be passed to @racket[raise-contract-error]. If possible, use this function instead of @racket[contract-val-first-projection] or @racket[contract-projection].