8.7 Building New Contract Combinators
(require racket/contract/combinator) | package: base |
procedure
(make-contract [ #:name name #:first-order test #:late-neg-projection late-neg-proj #:val-first-projection val-first-proj #:projection proj #:stronger stronger #:list-contract? is-list-contract?]) → contract? name : any/c = 'anonymous-contract test : (-> any/c any/c) = (λ (x) #t)
late-neg-proj : (or/c #f (-> blame? (-> any/c any/c any/c))) = #f
val-first-proj : (or/c #f (-> blame? (-> any/c (-> any/c any/c)))) = #f
proj : (-> blame? (-> any/c any/c)) =
(λ (b) (λ (x) (if (test x) x (raise-blame-error b x '(expected: "~a" given: "~e") name x)))) stronger : (or/c #f (-> contract? contract? boolean?)) = #f is-list-contract? : boolean? = #f
procedure
(make-chaperone-contract [ #:name name #:first-order test #:late-neg-projection late-neg-proj #:val-first-projection val-first-proj #:projection proj #:stronger stronger #:list-contract? is-list-contract?]) → chaperone-contract? name : any/c = 'anonymous-chaperone-contract test : (-> any/c any/c) = (λ (x) #t)
late-neg-proj : (or/c #f (-> blame? (-> any/c any/c any/c))) = #f
val-first-proj : (or/c #f (-> blame? (-> any/c (-> any/c any/c)))) = #f
proj : (-> blame? (-> any/c any/c)) =
(λ (b) (λ (x) (if (test x) x (raise-blame-error b x '(expected: "~a" given: "~e") name x)))) stronger : (or/c #f (-> contract? contract? boolean?)) = #f is-list-contract? : boolean? = #f
procedure
(make-flat-contract [ #:name name #:first-order test #:late-neg-projection late-neg-proj #:val-first-projection val-first-proj #:projection proj #:stronger stronger #:list-contract? is-list-contract?]) → flat-contract? name : any/c = 'anonymous-flat-contract test : (-> any/c any/c) = (λ (x) #t)
late-neg-proj : (or/c #f (-> blame? (-> any/c any/c any/c))) = #f
val-first-proj : (or/c #f (-> blame? (-> any/c (-> any/c any/c)))) = #f
proj : (-> blame? (-> any/c any/c)) =
(λ (b) (λ (x) (if (test x) x (raise-blame-error b x '(expected: "~a" given: "~e") name x)))) stronger : (or/c #f (-> contract? contract? boolean?)) = #f is-list-contract? : boolean? = #f
The name argument is any value to be rendered using display to describe the contract when a violation occurs. The default name for simple higher-order contracts is anonymous-contract, for chaperone contracts is anonymous-chaperone-contract, and for flat contracts is anonymous-flat-contract.
The first-order predicate test is used to determine which values the contract applies to. This test is used by contract-first-order-passes?, and indirectly by or/c and from-or/c to determine which higher-order contract to wrap a value with when there are multiple higher-order contracts to choose from. The default test accepts any value. The predicate should be influenced by the value of (contract-first-order-okay-to-give-up?) (see it’s documentation for more explanation).
The 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 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 with a chaperone or impersonator to enforce the contract), or signal a contract violation using raise-blame-error. The default is #f.
The projection proj and val-first-proj are older mechanisms for defining the behavior of applying the contract. The proj argument is a curried function of two arguments: the first application accepts a blame object, and the second accepts a value to protect with the contract. The projection must either produce the value, suitably wrapped to enforce any higher-order aspects of the contract, or signal a contract violation using raise-blame-error. The default projection produces an error when the first-order test fails, and produces the value unchanged otherwise. The val-first-proj is like late-neg-proj, except with an extra layer of currying.
At least one of the late-neg-proj, proj, val-first-proj, or first-order must be non-#f.
The projection arguments (late-neg-proj, proj, and val-first-proj) must be in sync with the test argument. In particular, if the test argument returns #f for some value, then the projections must raise a blame error for that value and if the test argument returns #t for some value, then the projection must not signal any blame for this value, unless there are higher-order interactions later. In other words, for flat contracts, the test and projection arguments must check the same predicate (which is why thee default projection uses the test argument directly).
Projections for chaperone contracts must produce a value that passes chaperone-of? when compared with the original, uncontracted value. Projections for flat contracts must fail precisely when the first-order test does, and must produce the input value unchanged otherwise. Applying a flat contract may result in either an application of the predicate, or the projection, or both; therefore, the two must be consistent. The existence of a separate projection only serves to provide more specific error messages. Most flat contracts do not need to supply an explicit projection.
The stronger argument is used to implement contract-stronger?. The first argument is always the contract itself and the second argument is whatever was passed as the second argument to contract-stronger?. If no stronger argument is supplied, then a default that compares its arguments with equal? is used.
The is-list-contract? argument is used by the list-contract? predicate to determine if this is a contract that accepts only list? values.
> (define int/c (make-flat-contract #:name 'int/c #:first-order integer?)) > (contract int/c 1 'positive 'negative) 1
> (contract int/c "not one" 'positive 'negative) eval:4:0: broke its own contract
promised: int/c
produced: "not one"
in: int/c
contract from: positive
blaming: positive
(assuming the contract is correct)
> (int/c 1) #t
> (int/c "not one") #f
> (define int->int/c (make-contract #:name 'int->int/c #:first-order (λ (x) (and (procedure? x) (procedure-arity-includes? x 1))) #:projection (λ (b) (let ([domain ((contract-projection int/c) (blame-swap b))] [range ((contract-projection int/c) b)]) (λ (f) (if (and (procedure? f) (procedure-arity-includes? f 1)) (λ (x) (range (f (domain x)))) (raise-blame-error b f '(expected "a function of one argument" given: "~e") f))))))) > (contract int->int/c "not fun" 'positive 'negative) eval:8:0: broke its own contract;
promised a function of one argument
produced: "not fun"
in: int->int/c
contract from: positive
blaming: positive
(assuming the contract is correct)
> (define halve (contract int->int/c (λ (x) (/ x 2)) 'positive 'negative)) > (halve 2) 1
> (halve 1/2) halve: contract violation
expected: int/c
given: 1/2
in: int->int/c
contract from: positive
blaming: negative
(assuming the contract is correct)
> (halve 1) halve: broke its own contract
promised: int/c
produced: 1/2
in: int->int/c
contract from: positive
blaming: positive
(assuming the contract is correct)
Changed in version 6.0.1.13 of package base: Added the #:list-contract? argument.
procedure
(build-compound-type-name c/s ...) → any
c/s : any/c
procedure
(coerce-contract id v) → contract?
id : symbol? v : any/c
If v is not one of the coercible values, coerce-contract signals an error, using the first argument in the error message.
procedure
(coerce-chaperone-contracts id vs)
→ (listof chaperone-contract?) id : symbol? vs : (listof any/c)
procedure
(coerce-flat-contract id v) → flat-contract?
id : symbol? v : any/c
procedure
(coerce-flat-contracts id v) → (listof flat-contract?)
id : symbol? v : (listof any/c)
procedure
(coerce-contract/f v) → (or/c contract? #f)
v : any/c
See make-contract for more details.
Added in version 6.1.1.5 of package base.
If c does not have a late-neg contract, then this function uses the original projection for it and logs a warning to the 'racket/contract logger.
See make-contract for more details.
Added in version 6.2.900.11 of package base.
parameter
(skip-projection-wrapper? wrap?) → void? wrap? : boolean?
= #f
syntax
(with-contract-continuation-mark blame body ...)
(with-contract-continuation-mark blame+neg-party body ...)
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 form. Otherwise (e.g., in the case of late-neg projections), a pair of the blame object and the negative party should be used instead.
Added in version 6.4.0.4 of package base.
8.7.1 Blame Objects
procedure
(raise-blame-error b v fmt v-fmt ...) → none/c
b : blame? v : any/c
fmt :
(or/c string? (listof (or/c string? 'given 'given: 'expected 'expected:))) v-fmt : any/c
The remaining arguments are a format string, fmt, and its arguments, v-fmt ..., specifying an error message specific to the precise violation.
If fmt is a list, then the elements are concatenated together (with spaces added, unless there are already spaces at the ends of the strings), after first replacing symbols with either their string counterparts, or replacing 'given with "produced" and 'expected with "promised", depending on whether or not the b argument has been swapped or not (see blame-swap).
If fmt contains the symbols 'given: or 'expected:, they are replaced like 'given: and 'expected: are, but the replacements are prefixed with the string "\n " to conform to the error message guidelines in Error Message Conventions.
procedure
(blame-add-context blame context [ #:important important #:swap? swap?]) → blame? blame : blame? context : (or/c string? #f) important : (or/c string? #f) = #f swap? : boolean? = #f
The context argument describes one layer of the portion of the contract, typically of the form "the 1st argument of" (in the case of a function contract) or "a conjunct of" (in the case of an and/c contract).
> (define/contract f (list/c (-> integer? integer?)) (list (λ (x) x))) > ((car f) #f) f: contract violation
expected: integer?
given: #f
in: the 1st argument of
the 1st element of
(list/c (-> integer? integer?))
contract from: (definition f)
blaming: top-level
(assuming the contract is correct)
at: eval:2.0
The important argument is used to build the beginning part of the contract violation. The last important argument that gets added to a blame object is used. The class/c contract adds an important argument, as does the -> contract (when -> knows the name of the function getting the contract).
The swap? argument has the effect of calling blame-swap while adding the layer of context, but without creating an extra blame object.
The context information recorded in blame structs keeps track of combinators that do not add information, and add the string "..." for them, so programmers at least see that there was some context they are missing in the error messages. Accordingly, since there are combinators that should not add any context (e.g., recursive-contract), passing #f as the context string argument avoids adding the "..." string.
procedure
(blame-positive b) → any/c
b : blame?
procedure
(blame-negative b) → any/c
b : blame?
procedure
(blame-contract b) → any/c
b : blame?
procedure
(blame-value b) → any/c
b : blame?
procedure
(blame-source b) → srcloc?
b : blame?
procedure
(blame-swap b) → blame?
b : blame?
procedure
(blame-original? b) → boolean?
b : blame?
procedure
(blame-swapped? b) → boolean?
b : blame?
procedure
(blame-replace-negative b neg) → blame?
b : blame? neg : any/c
procedure
(blame-update b pos neg) → blame?
b : blame? pos : any/c neg : any/c
procedure
(blame-missing-party? b) → boolean?
b : blame?
procedure
(blame-add-missing-party b missing-party)
→ (and/c blame? (not/c blame-missing-party?)) b : (and/c blame? blame-missing-party?) missing-party : any/c
struct
(struct exn:fail:contract:blame exn:fail:contract (object) #:extra-constructor-name make-exn:fail:contract:blame) object : blame?
the blame object for the violation,
the value that the contract applies to, and
a message indicating the kind of violation.
> (define (show-blame-error blame value message) (string-append "Contract Violation!\n" (format "Guilty Party: ~a\n" (blame-positive blame)) (format "Innocent Party: ~a\n" (blame-negative blame)) (format "Contracted Value Name: ~a\n" (blame-value blame)) (format "Contract Location: ~s\n" (blame-source blame)) (format "Contract Name: ~a\n" (blame-contract blame)) (format "Offending Value: ~s\n" value) (format "Offense: ~a\n" message))) > (current-blame-format show-blame-error)
> (define/contract (f x) (-> integer? integer?) (/ x 2)) > (f 2) 1
> (f 1) Contract Violation!
Guilty Party: (function f)
Innocent Party: top-level
Contracted Value Name: f
Contract Location: #(struct:srcloc eval 4 0 4 1)
Contract Name: (-> integer? integer?)
Offending Value: 1/2
Offense: promised: integer?
produced: 1/2
> (f 1/2) Contract Violation!
Guilty Party: top-level
Innocent Party: (function f)
Contracted Value Name: f
Contract Location: #(struct:srcloc eval 4 0 4 1)
Contract Name: (-> integer? integer?)
Offending Value: 1/2
Offense: expected: integer?
given: 1/2
8.7.2 Contracts as structs
The property prop:contract allows arbitrary structures to act as contracts. The property prop:chaperone-contract allows arbitrary structures to act as chaperone contracts; prop:chaperone-contract inherits prop:contract, so chaperone contract structures may also act as general contracts. The property prop:flat-contract allows arbitrary structures to act as flat contracts; prop:flat-contract inherits both prop:chaperone-contract and prop:procedure, so flat contract structures may also act as chaperone contracts, as general contracts, and as predicate procedures.
value
value
value
value
value
The value is expected to be the blame record for the contract on the value or a cons-pair of a blame record with a missing party and the missing party. The value-blame function reassembles the arguments of the pair into a complete blame record using blame-add-missing-party. If the value has one of the properties, but the value is not a blame object or a pair whose car position is a blame object, then has-blame? returns #f but value-blame returns #f.
procedure
(build-flat-contract-property [ #:name get-name #:first-order get-first-order #:late-neg-projection late-neg-proj #:val-first-projection val-first-proj #:projection get-projection #:stronger stronger #:generate generate #:exercise exercise #:list-contract? is-list-contract?]) → flat-contract-property?
get-name : (-> contract? any/c) = (λ (c) 'anonymous-flat-contract)
get-first-order : (-> contract? (-> any/c boolean?)) = (λ (c) (λ (x) #t))
late-neg-proj : (or/c #f (-> contract? (-> blame? (-> any/c any/c any/c)))) = #f
val-first-proj : (or/c #f (-> contract? blame? (-> any/c (-> any/c any/c)))) = #f
get-projection : (-> contract? (-> blame? (-> any/c any/c))) =
(λ (c) (λ (b) (λ (x) (if ((get-first-order c) x) x (raise-blame-error b x '(expected: "~a" given: "~e") (get-name c) x))))) stronger : (or/c (-> contract? contract? boolean?) #f) = #f
generate :
(->i ([c contract?]) ([generator (c) (-> (and/c positive? real?) (or/c (-> (or/c contract-random-generate-fail? c)) #f))])) = (λ (c) (λ (fuel) #f))
exercise :
(->i ([c contract?]) ([result (c) (-> (and/c positive? real?) (values (-> c void?) (listof contract?)))])) = (λ (c) (λ (fuel) (values void '()))) is-list-contract? : (-> contract? boolean?) = (λ (c) #f)
procedure
(build-chaperone-contract-property [ #:name get-name #:first-order get-first-order #:late-neg-projection late-neg-proj #:val-first-projection val-first-proj #:projection get-projection #:stronger stronger #:generate generate #:exercise exercise #:list-contract? is-list-contract?]) → chaperone-contract-property?
get-name : (-> contract? any/c) = (λ (c) 'anonymous-chaperone-contract)
get-first-order : (-> contract? (-> any/c boolean?)) = (λ (c) (λ (x) #t))
late-neg-proj : (or/c #f (-> contract? blame? (-> any/c any/c any/c))) = #f
val-first-proj : (or/c #f (-> contract? blame? (-> any/c (-> any/c any/c)))) = #f
get-projection : (-> contract? (-> blame? (-> any/c any/c))) =
(λ (c) (λ (b) (λ (x) (if ((get-first-order c) x) x (raise-blame-error b x '(expected: "~a" given: "~e") (get-name c) x))))) stronger : (or/c (-> contract? contract? boolean?) #f) = #f
generate :
(->i ([c contract?]) ([generator (c) (-> (and/c positive? real?) (or/c (-> (or/c contract-random-generate-fail? c)) #f))])) = (λ (c) (λ (fuel) #f))
exercise :
(->i ([c contract?]) ([result (c) (-> (and/c positive? real?) (values (-> c void?) (listof contract?)))])) = (λ (c) (λ (fuel) (values void '()))) is-list-contract? : (-> contract? boolean?) = (λ (c) #f)
procedure
(build-contract-property [ #:name get-name #:first-order get-first-order #:late-neg-projection late-neg-proj #:val-first-projection val-first-proj #:projection get-projection #:stronger stronger #:generate generate #:exercise exercise #:list-contract? is-list-contract?]) → contract-property? get-name : (-> contract? any/c) = (λ (c) 'anonymous-contract)
get-first-order : (-> contract? (-> any/c boolean?)) = (λ (c) (λ (x) #t))
late-neg-proj : (or/c #f (-> contract? blame? (-> any/c any/c any/c))) = #f
val-first-proj : (or/c #f (-> contract? blame? (-> any/c (-> any/c any/c)))) = #f
get-projection : (-> contract? (-> blame? (-> any/c any/c))) =
(λ (c) (λ (b) (λ (x) (if ((get-first-order c) x) x (raise-blame-error b x '(expected: "~a" given: "~e") (get-name c) x))))) stronger : (or/c (-> contract? contract? boolean?) #f) = #f
generate :
(->i ([c contract?]) ([generator (c) (-> (and/c positive? real?) (or/c (-> (or/c contract-random-generate-fail? c)) #f))])) = (λ (c) (λ (fuel) #f))
exercise :
(->i ([c contract?]) ([result (c) (-> (and/c positive? real?) (values (-> c void?) (listof contract?)))])) = (λ (c) (λ (fuel) (values void '()))) is-list-contract? : (-> contract? boolean?) = (λ (c) #f)
get-name which produces a description to write as part of a contract violation;
get-first-order, which produces a first-order predicate to be used by contract-first-order-passes?;
get-late-neg-projection, which produces a blame-tracking projection defining the behavior of the contract (The get-projection and get-val-first-projection arguments also specify the projection, but using a different signature. They are here for backwards compatibility.);
stronger, a predicate that determines whether this contract (passed in the first argument) is stronger than some other contract (passed in the second argument) and whose default always returns #f;
generate, which returns a thunk that generates random values matching the contract (using contract-random-generate-fail) to indicate failure) or #f to indicate that random generation for this contract isn’t supported;
exercise, which returns a function that exercises values matching the contract (e.g., if it is a function contract, it may call the function) and a list of contracts whose values will be generated by this process;
and is-list-contract?, which is used by flat-contract? to determine if this contract accepts only list?s.
At least one of the late-neg-proj, proj, val-first-proj, or first-order must be non-#f.
These accessors are passed as (optional) keyword arguments to build-contract-property, and are applied to instances of the appropriate structure type by the contract system. Their results are used analogously to the arguments of make-contract.
A chaperone contract property specifies the behavior of a structure when used as a chaperone contract. It is specified using build-chaperone-contract-property, and accepts exactly the same set of arguments as build-contract-property. The only difference is that the projection accessor must return a value that passes chaperone-of? when compared with the original, uncontracted value.
A flat contract property specifies the behavior of a structure when used as a flat contract. It is specified using build-flat-contract-property, and accepts exactly the same set of arguments as build-contract-property. The only difference is that the projection accessor is expected not to wrap its argument in a higher-order fashion, analogous to the constraint on projections in make-flat-contract.
Changed in version 6.0.1.13 of package base: Added the #:list-contract? argument. Changed in version 6.1.1.4: Allow generate to return contract-random-generate-fail
procedure
(contract-property? v) → boolean?
v : any/c
procedure
v : any/c
procedure
v : any/c
8.7.3 Obligation Information in Check Syntax
Check Syntax in DrRacket shows obligation information for contracts according to syntax-propertys that the contract combinators leave in the expanded form of the program. These properties indicate where contracts appear in the source and where the positive and negative positions of the contracts appear.
To make Check Syntax show obligation information for your new contract combinators, use the following properties (some helper macros and functions are below):
- This property should be attached to the result of a transformer that implements a contract combinator. It signals to Check Syntax that this is where a contract begins.
The first element in the vector should be a unique (in the sense of eq?) value that Check Syntax can use a tag to match up this contract with its subpieces (specified by the two following syntax properties).
The second and third elements of the vector are syntax objects from pieces of the contract, and Check Syntax will color them. The first list should contain subparts that are the responsibility of parties (typically modules) that provide implementations of the contract. The second list should contain subparts that are the responsibility of clients.
For example, in (->* () #:pre #t any/c #:post #t), the ->* and the #:post should be in the first list and #:pre in the second list.
- This property should be attached to sub-expressions of a contract combinator that are expected to be other contracts. The value of the property should be the key (the first element from the vector for the 'racket/contract:contract property) indicating which contract this is.
This property should be used when the expression’s value is a contract that clients are responsible for.
- This form is just like 'racket/contract:negative-position, except that it should be used when the expression’s value is a contract that the original party should be responsible for.
- The presence of this property tells Check Syntax that it should start coloring from this point. It expects the expression to be a contract (and, thus, to have the 'racket/contract:contract property); this property indicates that this contract is on a (module) boundary.
(The value of the property is not used.)
- Like 'racket/contract:contract-on-boundary, the presence of this property triggers coloring, but this is meant for use when the party (module) containing the contract (regardless of whether or not this module exports anything matching the contract) can be blamed for violating the contract. This comes into play for ->i contracts, since the contract itself has access to values under contract via the dependency.
syntax
(define/final-prop header body ...)
header = main-id | (main-id id ...) | (main-id id ... . id)
syntax
(define/subexpression-pos-prop header body ...)
header = main-id | (main-id id ...) | (main-id id ... . id)
8.7.4 Utilities for Building New Combinators
procedure
(contract-stronger? c1 c2) → boolean?
c1 : contract? c2 : contract?
Contracts that are the same (i.e., where c1 is equal? to c2) are considered to always be stronger than each other.
This function is conservative, so it may return #f when c1 does, in fact, accept fewer values.
> (contract-stronger? integer? integer?) #t
> (contract-stronger? (between/c 25 75) (between/c 0 100)) #t
> (contract-stronger? (between/c 0 100) (between/c 25 75)) #f
> (contract-stronger? (between/c -10 0) (between/c 0 10)) #f
> (contract-stronger? (λ (x) (and (real? x) (<= x 0))) (λ (x) (and (real? x) (<= x 100)))) #f
procedure
(contract-first-order-passes? contract v) → boolean?
contract : contract? v : any/c
If it returns #f, the contract is guaranteed not to hold for that value; if it returns #t, the contract may or may not hold. If the contract is a first-order contract, a result of #t guarantees that the contract holds.
See also contract-first-order-okay-to-give-up? and contract-first-order-try-less-hard.
procedure
(contract-first-order c) → (-> any/c boolean?)
c : contract?