diff --git a/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts.scrbl b/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts.scrbl index c1eab95fb8..21e24a2e97 100644 --- a/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts.scrbl +++ b/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts.scrbl @@ -41,4 +41,5 @@ update string-pad-center to show examples via REPL notation: @include-section["contracts/structure.scrbl"] @include-section["contracts/exists.scrbl"] @include-section["contracts/examples.scrbl"] +@include-section["contracts/new-combinators.scrbl"] @include-section["contracts/gotchas.scrbl"] diff --git a/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts/new-combinators.scrbl b/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts/new-combinators.scrbl new file mode 100644 index 0000000000..4be0422070 --- /dev/null +++ b/pkgs/racket-pkgs/racket-doc/scribblings/guide/contracts/new-combinators.scrbl @@ -0,0 +1,472 @@ +#lang scribble/doc +@(require scribble/manual scribble/eval "utils.rkt" + (for-label racket/contract racket/gui)) + +@(define ex-eval (make-base-eval)) +@(ex-eval '(require racket/contract)) + +@title{Building New Contracts} + +Contracts are represented internally as functions that +accept information about the contract (who is to blame, +source locations, @|etc|) and produce projections (in the +spirit of Dana Scott) that enforce the contract. A +projection is a function that accepts an arbitrary value, +and returns a value that satisfies the corresponding +contract. For example, a projection that accepts only +integers corresponds to the contract @racket[(flat-contract +integer?)], and can be written like this: + +@racketblock[ +(define int-proj + (λ (x) + (if (integer? x) + x + (signal-contract-violation)))) +] + +As a second example, a projection that accepts unary functions +on integers looks like this: + +@racketblock[ +(define int->int-proj + (λ (f) + (if (and (procedure? f) + (procedure-arity-includes? f 1)) + (λ (x) (int-proj (f (int-proj x)))) + (signal-contract-violation)))) +] + +Although these projections have the right error behavior, +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 +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 +can then, in turn, pass that information +to @racket[raise-blame-error] to signal a good error +message. + +Here is the first of those two projections, rewritten for +use in the contract system: +@racketblock[ +(define (int-proj blame) + (λ (x) + (if (integer? x) + x + (raise-blame-error + blame + x + '(expected: "" given: "~e") + x)))) +] +The new argument specifies who is to be blamed for +positive and negative contract violations. + +Contracts, in this system, are always +established between two parties. One party provides some +value according to the contract, and the other consumes the +value, also according to the contract. The first is called +the ``positive'' person and the second the ``negative''. So, +in the case of just the integer contract, the only thing +that can go wrong is that the value provided is not an +integer. Thus, only the positive party can ever accrue +blame. The @racket[raise-blame-error] function always blames +the positive party. + +Compare that to the projection for our function contract: + +@racketblock[ +(define (int->int-proj blame) + (define dom (int-proj (blame-swap blame))) + (define rng (int-proj blame)) + (λ (f) + (if (and (procedure? f) + (procedure-arity-includes? f 1)) + (λ (x) (rng (f (dom x)))) + (raise-blame-error + blame + f + '(expected "a procedure of one argument" given: "~e") + f)))) +] + +In this case, the only explicit blame covers the situation +where either a non-procedure is supplied to the contract or +the procedure does not accept one argument. As with +the integer projection, the blame here also lies with the +producer of the value, which is +why @racket[raise-blame-error] is passed @racket[blame] unchanged. + +The checking for the domain and range are delegated to +the @racket[int-proj] function, which is supplied its +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 +the positive party with the negative party and vice versa. + +This technique is not merely a cheap trick to get the example to work, +however. The reversal of the positive and the negative is a +natural consequence of the way functions behave. That is, +imagine the flow of values in a program between two +modules. First, one module defines a function, and then that +module is required by another. So far, the function itself +has to go from the original, providing module to the +requiring module. Now, imagine that the providing module +invokes the function, supplying it an argument. At this +point, the flow of values reverses. The argument is +traveling back from the requiring module to the providing +module! And finally, when the function produces a result, +that result flows back in the original +direction. Accordingly, the contract on the domain reverses +the positive and the negative blame parties, just like the flow +of values reverses. + +We can use this insight to generalize the function contracts +and build a function that accepts any two contracts and +returns a contract for functions between them. + +This projection also goes further and uses +@racket[blame-add-context] to improve the error messages +when a contract violation is detected. + +@racketblock[ +(define (make-simple-function-contract dom-proj range-proj) + (λ (blame) + (define dom (dom-proj (blame-add-context blame + "the argument of" + #:swap? #t))) + (define rng (range-proj (blame-add-context blame + "the range of"))) + (λ (f) + (if (and (procedure? f) + (procedure-arity-includes? f 1)) + (λ (x) (rng (f (dom x)))) + (raise-blame-error + blame + f + '(expected "a procedure of one argument" given: "~e") + f))))) +] + +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{val first projection} accepts +a blame object without the negative blame information and then +returns a function that accepts the value to be contracted, and +then finally accepts the name of the negative party to the contract +before returning the value with the contract. Rewriting @racket[int->int-proj] +to use this API looks like this: +@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) + (if (and (procedure? f) + (procedure-arity-includes? f 1)) + (λ (neg-party) + (λ (x) + (check-int x dom-blame neg-party) + (define ans (f x)) + (check-int ans rng-blame neg-party) + ans)) + (λ (neg-party) + (raise-blame-error + blame #:missing-party neg-party + f + '(expected "a procedure of one argument" given: "~e") + f)))))] +The advantage of this style of contract is that the @racket[_blame] +and @racket[_v] arguments can be supplied on the server side of the +contract boundary and the result can be used for every different +client. With the simpler situation, a new blame object has to be +created for each client. + +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 +@racket[make-chaperone-contract] to build it: +@interaction/no-prompt[#:eval ex-eval + (define int->int-contract + (make-contract + #:name 'int->int + #:val-first-projection int->int-proj))] +and then combine it with a value and get some contract +checking. +@def+int[#:eval + ex-eval + (define/contract (f x) + int->int-contract + "not an int") + (f #f) + (f 1)] + +@section{Contract Struct Properties} + +The @racket[make-chaperone-contract] function is okay for one-off contracts, +but often you want to make many different contracts that differ only +in some pieces. The best way to do that is to use a @racket[struct] +with either @racket[prop:contract], @racket[prop:chaperone-contract], or +@racket[prop:flat-contract]. + +For example, lets say we wanted to make a simple form of the @racket[->] +contract that accepts one contract for the range and one for the domain. +We should define a struct with two fields and use +@racket[build-chaperone-contract-property] to construct the chaperone contract +property we need. +@interaction/no-prompt[#:eval ex-eval + (struct simple-arrow (dom rng) + #:property prop:chaperone-contract + (build-chaperone-contract-property + #:name + (λ (arr) (simple-arrow-name arr)) + #:val-first-projection + (λ (arr) (simple-arrow-val-first-proj arr))))] + +To do the automatic coercion of values like @racket[integer?] and @racket[#f] +into contracts, we need to call @racket[coerce-chaperone-contract] +(note that this rejects impersonator contracts and does not insist +on flat contracts; to do either of those things, call @racket[coerce-contract] +or @racket[coerce-flat-contract] instead). +@interaction/no-prompt[#:eval ex-eval + (define (simple-arrow-contract dom rng) + (simple-arrow (coerce-contract 'simple-arrow-contract dom) + (coerce-contract 'simple-arrow-contract rng)))] + +To define @racket[_simple-arrow-name] is straight-forward; it needs to return +an s-expression representing the contract: +@interaction/no-prompt[#:eval ex-eval + (define (simple-arrow-name arr) + `(-> ,(contract-name (simple-arrow-dom arr)) + ,(contract-name (simple-arrow-rng arr))))] +And we can define the projection using a generalization of the +projection we defined earlier, this time using +@tech[#:doc '(lib "scribblings/reference/reference.scrbl")]{chaperones}: +@interaction/no-prompt[#:eval + ex-eval + (define (simple-arrow-val-first-proj arr) + (define dom-ctc (get/build-val-first-projection (simple-arrow-dom arr))) + (define rng-ctc (get/build-val-first-projection (simple-arrow-rng arr))) + (λ (blame) + (define dom+blame (dom-ctc (blame-add-context blame + "the argument of" + #:swap? #t))) + (define rng+blame (rng-ctc (blame-add-context blame "the range of"))) + (λ (f) + (if (and (procedure? f) + (procedure-arity-includes? f 1)) + (λ (neg-party) + (chaperone-procedure + f + (λ (arg) + (values + (λ (result) ((rng+blame result) neg-party)) + ((dom+blame arg) neg-party))))) + (λ (neg-party) + (raise-blame-error + blame #:missing-party neg-party + f + '(expected "a procedure of one argument" given: "~e") + f))))))] + +@def+int[#:eval + ex-eval + (define/contract (f x) + (simple-arrow-contract integer? boolean?) + "not a boolean") + (f #f) + (f 1)] + +@section{With all the Bells and Whistles} + +There are a number of optional pieces to a contract that +@racket[simple-arrow-contract] did not add. In this section, +we walk through all of them to show examples of how they can +be implemented. + +The first is a first-order check. This is used by @racket[or/c] +in order to determine which of the higher-order argument contracts +to use when it sees a value. Here's the function for +our simple arrow contract. +@interaction/no-prompt[#:eval ex-eval + (define (simple-arrow-first-order ctc) + (λ (v) (and (procedure? v) + (procedure-arity-includes? v 1))))] +It accepts a value and returns @racket[#f] if the value is guaranteed not +to satisfy the contract, and @racket[#t] if, as far as we can tell, +the value satisfies the contract, just be inspecting first-order +properties of the value. + +The next is random generation. Random generation in the contract +library consists of two pieces: the ability to randomly generate +values satisfying the contract and the ability to exercise values +that match the contract that are given, in the hopes of finding bugs +in them (and also to try to get them to produce interesting values to +be used elsewhere during generation). + +To exercise contracts, we need to implement a function that +is given a @racket[arrow-contract] struct and some fuel. It should return +two values: a function that accepts values of the contract +and exercises them, plus a list of values that the exercising +process will always produce. In the case of our simple +contract, we know that we can always produce values of the range, +as long as we can generate values of the domain (since we can just +call the function). So, here's a function that matches the +@racket[_exercise] argument of @racket[build-chaperone-contract-property]'s +contract: +@interaction/no-prompt[#:eval + ex-eval + (define (simple-arrow-contract-exercise arr) + (define env (contract-random-generate-get-current-environment)) + (λ (fuel) + (define dom-generate + (contract-random-generate/choose (simple-arrow-dom arr) fuel)) + (cond + [dom-generate + (values + (λ (f) (contract-random-generate-stash + env + (simple-arrow-rng arr) + (f (dom-generate)))) + (list (simple-arrow-rng arr)))] + [else + (values void '())])))] +If the domain contract can be generated, then we know we can do some good via exercising. +In that case, we return a procedure that calls @racket[_f] (the function matching +the contract) with something that we generated from the domain, and we stash the result +value in the environment too. We also return @racket[(simple-arrow-rng arr)] +to indicate that exercising will always produce something of that contract. + +If we cannot, then we simply return a function that +does no exercising (@racket[void]) and the empty list (indicating that we won't generate +any values). + +Then, to generate values matching the contract, we define a function +that when given the contract and some fuel, makes up a random function. +To help make it a more effective testing function, we can exercise +any arguments it receives, and also stash them into the generation +environment, but only if we can generate values of the range contract. +@interaction/no-prompt[#:eval + ex-eval + (define (simple-arrow-contract-generate arr) + (λ (fuel) + (define env (contract-random-generate-get-current-environment)) + (define rng-generate + (contract-random-generate/choose (simple-arrow-rng arr) fuel)) + (cond + [rng-generate + (λ () + (λ (arg) + (contract-random-generate-stash env (simple-arrow-dom arr) arg) + (rng-generate)))] + [else + #f])))] + +When the random generation pulls something out of the environment, +it needs to be able to tell if a value that has been passed to +@racket[contract-random-generate-stash] is a candidate for +the contract it is trying to generate. Of course, it the contract +passed to @racket[contract-random-generate-stash] is an exact +match, then it can use it. But it can also use the value if the +contract is stronger (in the sense that it accepts fewer values). + +To provide that functionality, we implement this function: +@interaction/no-prompt[#:eval ex-eval + (define (simple-arrow-first-stronger? this that) + (and (simple-arrow? that) + (contract-stronger? (simple-arrow-dom that) + (simple-arrow-dom this)) + (contract-stronger? (simple-arrow-rng this) + (simple-arrow-rng that))))] +This function accepts @racket[_this] and @racket[_that], two contracts. It is +guaranteed that @racket[_this] will be one of our simple arrow contracts, +since we're supplying this function together with the simple arrow implementation. +But the @racket[_that] argument might be any contract. This function +checks to see if @racket[_that] is also a simple arrow contract and, if so +compares the domain and range. Of course, there are other contracts that we +could also check for (e.g., contracts built using @racket[->] or @racket[->*]), +but we do not need to. The stronger function is allowed to return @racket[#f] +if it doesn't know the answer but if it returns @racket[#t], then the contract +really must be stronger. + +Now that we have all of the pieces implemented, we need to pass them +to @racket[build-chaperone-contract-property] so the contract system +starts using them: +@interaction/no-prompt[#:eval ex-eval + (struct simple-arrow (dom rng) + #:property prop:custom-write contract-custom-write-property-proc + #:property prop:chaperone-contract + (build-chaperone-contract-property + #:name + (λ (arr) (simple-arrow-name arr)) + #:val-first-projection + (λ (arr) (simple-arrow-val-first-proj arr)) + #:first-order simple-arrow-first-order + #:stronger simple-arrow-first-stronger? + #:generate simple-arrow-contract-generate + #:exercise simple-arrow-contract-exercise)) + + (define (simple-arrow-contract dom rng) + (simple-arrow (coerce-contract 'simple-arrow-contract dom) + (coerce-contract 'simple-arrow-contract rng)))] +We also add a @racket[prop:custom-write] property so +that the contracts print properly, e.g.: +@interaction[#:eval ex-eval (simple-arrow-contract integer? integer?)] +(We use @racket[prop:custom-write] because the contract library +can not depend on @racketmod[racket/generic] but yet still wants +to provide some help to make it easy to use the right printer.) + +Now that that's done, we can use the new functionality. Here's a random function, +generated by the contract library, using our @racket[simple-arrow-contract-generate] +function: +@def+int[#:eval + ex-eval + (define a-random-function + (contract-random-generate + (simple-arrow-contract integer? integer?))) + (a-random-function 0) + (a-random-function 1)] + +Here's how the contract system can now automatically find bugs in functions +that consume simple arrow contracts: +@def+int[#:eval + ex-eval + (define/contract (misbehaved-f f) + (-> (simple-arrow-contract integer? boolean?) any) + (f "not an integer")) + (contract-exercise misbehaved-f)] + +And if we hadn't implemented @racket[simple-arrow-first-order], then +@racket[or/c] would not be able to tell which branch of the @racket[or/c] +to use in this program: +@def+int[#:eval + ex-eval + (define/contract (maybe-accepts-a-function f) + (or/c (simple-arrow-contract real? real?) + (-> real? real? real?) + real?) + (if (procedure? f) + (if (procedure-arity-includes f 1) + (f 1132) + (f 11 2)) + f)) + (maybe-accepts-a-function sqrt) + (maybe-accepts-a-function 123)] + + +@(close-eval ex-eval) diff --git a/pkgs/racket-pkgs/racket-doc/scribblings/reference/contracts.scrbl b/pkgs/racket-pkgs/racket-doc/scribblings/reference/contracts.scrbl index 9e4c862b2f..e4f95c815e 100644 --- a/pkgs/racket-pkgs/racket-doc/scribblings/reference/contracts.scrbl +++ b/pkgs/racket-pkgs/racket-doc/scribblings/reference/contracts.scrbl @@ -9,6 +9,11 @@ (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}) + @title[#:tag "contracts" #:style 'toc]{Contracts} @guideintro["contracts"]{contracts} @@ -1704,199 +1709,6 @@ accepted by the third argument to @racket[datum->syntax]. @defmodule*/no-declare[(racket/contract/combinator)] @declare-exporting-ctc[racket/contract/combinator] -Contracts are represented internally as functions that -accept information about the contract (who is to blame, -source locations, @|etc|) and produce projections (in the -spirit of Dana Scott) that enforce the contract. A -projection is a function that accepts an arbitrary value, -and returns a value that satisfies the corresponding -contract. For example, a projection that accepts only -integers corresponds to the contract @racket[(flat-contract -integer?)], and can be written like this: - -@racketblock[ -(define int-proj - (λ (x) - (if (integer? x) - x - (signal-contract-violation)))) -] - -As a second example, a projection that accepts unary functions -on integers looks like this: - -@racketblock[ -(define int->int-proj - (λ (f) - (if (and (procedure? f) - (procedure-arity-includes? f 1)) - (λ (x) (int-proj (f (int-proj x)))) - (signal-contract-violation)))) -] - -Although these projections have the right error behavior, -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 -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 -can then, in turn, pass that information -to @racket[raise-blame-error] to signal a good error -message. - -Here is the first of those two projections, rewritten for -use in the contract system: -@racketblock[ -(define (int-proj blame) - (λ (x) - (if (integer? x) - x - (raise-blame-error - blame - x - '(expected: "" given: "~e") - x)))) -] -The new argument specifies who is to be blamed for -positive and negative contract violations. - -Contracts, in this system, are always -established between two parties. One party provides some -value according to the contract, and the other consumes the -value, also according to the contract. The first is called -the ``positive'' person and the second the ``negative''. So, -in the case of just the integer contract, the only thing -that can go wrong is that the value provided is not an -integer. Thus, only the positive party can ever accrue -blame. The @racket[raise-blame-error] function always blames -the positive party. - -Compare that to the projection for our function contract: - -@racketblock[ -(define (int->int-proj blame) - (define dom (int-proj (blame-swap blame))) - (define rng (int-proj blame)) - (λ (f) - (if (and (procedure? f) - (procedure-arity-includes? f 1)) - (λ (x) (rng (f (dom x)))) - (raise-blame-error - blame - f - '(expected "a procedure of one argument" given: "~e") - f)))) -] - -In this case, the only explicit blame covers the situation -where either a non-procedure is supplied to the contract or -the procedure does not accept one argument. As with -the integer projection, the blame here also lies with the -producer of the value, which is -why @racket[raise-blame-error] is passed @racket[blame] unchanged. - -The checking for the domain and range are delegated to -the @racket[int-proj] function, which is supplied its -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 -the positive party with the negative party and vice versa. - -This technique is not merely a cheap trick to get the example to work, -however. The reversal of the positive and the negative is a -natural consequence of the way functions behave. That is, -imagine the flow of values in a program between two -modules. First, one module defines a function, and then that -module is required by another. So far, the function itself -has to go from the original, providing module to the -requiring module. Now, imagine that the providing module -invokes the function, supplying it an argument. At this -point, the flow of values reverses. The argument is -traveling back from the requiring module to the providing -module! And finally, when the function produces a result, -that result flows back in the original -direction. Accordingly, the contract on the domain reverses -the positive and the negative blame parties, just like the flow -of values reverses. - -We can use this insight to generalize the function contracts -and build a function that accepts any two contracts and -returns a contract for functions between them. - -This projection also goes further and uses -@racket[blame-add-context] to improve the error messages -when a contract violation is detected. - -@racketblock[ -(define (make-simple-function-contract dom-proj range-proj) - (λ (blame) - (define dom (dom-proj (blame-add-context blame - "the argument of" - #:swap? #t))) - (define rng (range-proj (blame-add-context blame - "the range of"))) - (λ (f) - (if (and (procedure? f) - (procedure-arity-includes? f 1)) - (λ (x) (rng (f (dom x)))) - (raise-blame-error - blame - f - '(expected "a procedure of one argument" given: "~e") - f))))) -] - -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{val first projection} accepts -a blame object without the negative blame information and then -returns a function that accepts the value to be contracted, and -then finally accepts the name of the negative party to the contract -before returning the value with the contract. Rewriting @racket[int->int-proj] -to use this API looks like this: -@racketblock[ -(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) - (if (and (procedure? f) - (procedure-arity-includes? f 1)) - (λ (neg-party) - (λ (x) - (check-int x dom-blame neg-party) - (define ans (f x)) - (check-int ans rng-blame neg-party) - ans)) - (λ (neg-party) - (raise-blame-error - blame #:missing-party neg-party - f - '(expected "a procedure of one argument" given: "~e") - f)))))] -The advantage of this style of contract is that the @racket[_blame] -and @racket[_v] arguments can be supplied on the server side of the -contract boundary and the result can be used for every different -client. With the simpler situation, a new blame object has to be -created for each client. - -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 below. @deftogether[( @defproc[(make-contract @@ -2095,10 +1907,19 @@ contracts. The error messages assume that the function named by the value cannot be coerced to a contract. } +@defproc[(get/build-val-first-projection [c contract?]) + (-> contract? blame? (-> any/c (-> any/c any/c)))]{ + Returns the @racket[_val-first] projection for @racket[c]. + + See @racket[make-contract] for more details. + +@history[#:added "6.1.1.5"] +} + @subsection{Blame Objects} @defproc[(blame? [x any/c]) boolean?]{ -This predicate recognizes @tech{blame objects}. + This predicate recognizes @|blame-objects|. } @defproc[(blame-add-context [blame blame?] @@ -2131,13 +1952,13 @@ the @racket[list/c] combinators each internally called The @racket[important] argument is used to build the beginning part of the contract violation. The last @racket[important] argument that -gets added to a blame object is used. The @racket[class/c] contract +gets added to a @|blame-object| is used. The @racket[class/c] contract adds an important argument, as does the @racket[->] contract (when @racket[->] knows the name of the function getting the contract). The @racket[swap?] argument has the effect of calling @racket[blame-swap] while adding the layer of context, but without creating an extra -blame object. +@|blame-object|. The context information recorded in blame structs keeps track of combinators that do not add information, and add the string @racket["..."] @@ -2153,7 +1974,7 @@ passing @racket[#f] as the context string argument avoids adding the @defproc[(blame-negative [b blame?]) any/c] )]{ These functions produce printable descriptions of the current positive and -negative parties of a blame object. +negative parties of a @|blame-object|. } @defproc[(blame-contract [b blame?]) any/c]{ @@ -2173,7 +1994,7 @@ source location was provided, all fields of the structure will contain } @defproc[(blame-swap [b blame?]) blame?]{ -This function swaps the positive and negative parties of a @tech{blame object}. +This function swaps the positive and negative parties of a @|blame-object|. (See also @racket[blame-add-context].) } @@ -2182,7 +2003,7 @@ This function swaps the positive and negative parties of a @tech{blame object}. @defproc[(blame-swapped? [b blame?]) boolean?] )]{ -These functions report whether the current blame of a given blame object is the +These functions report whether the current blame of a given @|blame-object| is the same as in the original contract invocation (possibly of a compound contract containing the current one), or swapped, respectively. Each is the negation of the other; both are provided for convenience and clarity. @@ -2236,7 +2057,7 @@ to the error message guidelines in @secref["err-msg-conventions"]. @defstruct[(exn:fail:contract:blame exn:fail:contract) ([object blame?])]{ This exception is raised to signal a contract error. The @racket[object] - field contains a @tech{blame object} associated with a contract violation. + field contains a @|blame-object| associated with a contract violation. } @defparam[current-blame-format @@ -2247,7 +2068,7 @@ A @tech{parameter} that is used when constructing a contract violation error. Its value is procedure that accepts three arguments: @itemize[ -@item{the blame object for the violation,} +@item{the @|blame-object| for the violation,} @item{the value that the contract applies to, and} @item{a message indicating the kind of violation.}] The procedure then @@ -2733,7 +2554,7 @@ Produces the name used to describe the contract in error messages. } @defproc[(value-blame [v has-blame?]) (or/c blame? #f)]{ - Returns the blame object for the contract attached + Returns the @|blame-object| for the contract attached to @racket[v], if recorded. Otherwise it returns @racket[#f]. To support @racket[value-contract] and @racket[value-blame] @@ -2835,10 +2656,19 @@ makes a binary search tree contract, but one that is @defthing[contract-continuation-mark-key continuation-mark-key?]{ Key used by continuation marks that are present during contract checking. -The value of these marks are the blame objects that correspond to the contract +The value of these marks are the @|blame-objects| that correspond to the contract currently being checked. } +@defproc[(contract-custom-write-property-proc [c contract?] + [p output-port?] + [mode (or/c #f #t 0 1)]) + void?]{ + Prints @racket[c] to @racket[p] using the contract's name. + + @history[#:added "6.1.1.5"] +} + @section{@racketmodname[racket/contract/base]} @defmodule[racket/contract/base] @@ -3006,4 +2836,4 @@ ended up returning @racket[contract-random-generate-fail]. while generation is happening. @history[#:added "6.1.1.5"] -} \ No newline at end of file +} diff --git a/racket/collects/racket/contract.rkt b/racket/collects/racket/contract.rkt index 07c699fe04..e744348c81 100644 --- a/racket/collects/racket/contract.rkt +++ b/racket/collects/racket/contract.rkt @@ -18,4 +18,6 @@ contract-random-generate/choose contract-random-generate-fail contract-random-generate-fail? - contract-exercise) + contract-exercise + get/build-val-first-projection + contract-custom-write-property-proc) diff --git a/racket/collects/racket/contract/private/guts.rkt b/racket/collects/racket/contract/private/guts.rkt index 0fa1e9f08d..84d74e4e46 100644 --- a/racket/collects/racket/contract/private/guts.rkt +++ b/racket/collects/racket/contract/private/guts.rkt @@ -49,9 +49,10 @@ contract-continuation-mark-key (struct-out wrapped-extra-arg-arrow) - custom-write-property-proc) + contract-custom-write-property-proc + (rename-out [contract-custom-write-property-proc custom-write-property-proc])) -(define (custom-write-property-proc stct port display?) +(define (contract-custom-write-property-proc stct port display?) (write-string "#<" port) (cond [(flat-contract-struct? stct) (write-string "flat-" port)] @@ -367,7 +368,7 @@ ; (define-struct eq-contract (val name) - #:property prop:custom-write custom-write-property-proc + #:property prop:custom-write contract-custom-write-property-proc #:property prop:flat-contract (build-flat-contract-property #:first-order (λ (ctc) (λ (x) (eq? (eq-contract-val ctc) x))) @@ -387,7 +388,7 @@ #:list-contract? (λ (c) (null? (eq-contract-val c))))) (define-struct equal-contract (val name) - #:property prop:custom-write custom-write-property-proc + #:property prop:custom-write contract-custom-write-property-proc #:property prop:flat-contract (build-flat-contract-property #:first-order (λ (ctc) (λ (x) (equal? (equal-contract-val ctc) x))) @@ -406,7 +407,7 @@ (λ (fuel) (λ () v))))) (define-struct =-contract (val name) - #:property prop:custom-write custom-write-property-proc + #:property prop:custom-write contract-custom-write-property-proc #:property prop:flat-contract (build-flat-contract-property #:first-order (λ (ctc) (λ (x) (and (number? x) (= (=-contract-val ctc) x)))) @@ -425,7 +426,7 @@ (λ (fuel) (λ () v))))) (define-struct regexp/c (reg name) - #:property prop:custom-write custom-write-property-proc + #:property prop:custom-write contract-custom-write-property-proc #:property prop:flat-contract (build-flat-contract-property #:first-order @@ -443,7 +444,7 @@ ;; sane? : boolean -- indicates if we know that the predicate is well behaved ;; (for now, basically amounts to trusting primitive procedures) (define-struct predicate-contract (name pred generate sane?) - #:property prop:custom-write custom-write-property-proc + #:property prop:custom-write contract-custom-write-property-proc #:property prop:flat-contract (build-flat-contract-property #:stronger