7.6 Building New Contract Combinators
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 (flat-contract integer?), and can be written like this:
As a second example, a projection that accepts unary functions
on integers looks like this:
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
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 raise-blame-error to signal a good error
message.
Here is the first of those two projections, rewritten for
use in the contract system:
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 raise-blame-error function always blames
the positive party.
Compare that to the projection for our function contract:
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 raise-blame-error is passed blame unchanged.
The checking for the domain and range are delegated to
the int-proj function, which is supplied its
arguments in the first two lines of
the int->int-proj function. The trick here is that,
even though the int->int-proj function always
blames what it sees as positive, we can swap the blame parties by
calling blame-swap on the given 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, suppying 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.
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.
These functions build simple higher-order contracts, chaperone contracts, and flat contracts,
respectively. They both take the same set of three optional arguments: a name,
a first-order predicate, and a blame-tracking projection.
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 can be used to determine which values
the contract applies to; usually, this is the set of values for which the
contract fails immediately without any higher-order wrapping. This test is used
by contract-first-order-passes?, and indirectly by or/c to
determine which of multiple higher-order contracts to wrap a value with. The
default test accepts any value.
The projection proj defines the behavior of applying the contract. It
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.
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.
Examples: |
| > (contract int/c 1 'positive 'negative) | 1 | > (contract int/c "not one" 'positive 'negative) | eval:4:0: self-contract violation, expected <int/c>, given: | "not one" | contract from positive, blaming positive | contract: int/c | > (int/c 1) | #t | > (int/c "not one") | #f | | > (contract int->int/c "not fun" 'positive 'negative) | eval:8:0: self-contract violation, expected a function of | one argument, got: "not fun" | contract from positive, blaming positive | contract: int->int/c | | > (halve 2) | 1 | > (halve 1/2) | halve: contract violation, expected <int/c>, given: 1/2 | contract from positive, blaming negative | contract: int->int/c | > (halve 1) | halve: self-contract violation, expected <int/c>, given: 1/2 | contract from positive, blaming positive | contract: int->int/c |
|
Produces an S-expression to be used as a name
for a contract. The arguments should be either contracts or
symbols. It wraps parentheses around its arguments and
extracts the names from any contracts it is supplied with.
Converts a regular Racket value into an instance of a contract struct,
converting it according to the description of
contracts.
If x is not one of the coercible values,
coerce-contract signals an error, using the first argument in
the error message.
Coerces all of the arguments in ’xs’ into contracts (via
coerce-contract/f) and signals an error if any of them are not
contracts. The error messages assume that the function named by
id got
xs as its entire argument list.
Like
coerce-contract, but requires the result
to be a chaperone contract, not an arbitrary contract.
Like
coerce-contracts, but requires the results
to be chaperone contracts, not arbitrary contracts.
Like
coerce-contract, but requires the result
to be a flat contract, not an arbitrary contract.
Like
coerce-contracts, but requires the results
to be flat contracts, not arbitrary contracts.
Like
coerce-contract, but returns
#f if
the value cannot be coerced to a contract.
7.6.1 Blame Objects
These functions produce printable descriptions of the current positive and
negative parties of a blame object.
This function produces a description of the contract associated with a blame
object (the result of
contract-name).
This function produces the name of the value to which the contract was applied,
or #f if no name was provided.
This function produces the source location associated with a contract. If no
source location was provided, all fields of the structure will contain
#f.
This function swaps the positive and negative parties of a
blame object.
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.
Produces a
blame? object just like
b except
that it uses
neg instead of the negative
position
b has.
Signals a contract violation. The first argument,
b, records the
current blame information, including positive and negative parties, the name of
the contract, the name of the value, and the source location of the contract
application. The second argument,
x, is the value that failed to
satisfy the contract. The remaining arguments are a format string,
fmt, and its arguments,
v ..., specifying an error message
specific to the precise violation.
This exception is raised to signal a contract error. The
blame
field extracts the
blame? object associated with a contract violation.
A parameter that is used when constructing a
contract violation error. Its value is procedure that
accepts three arguments:
the blame object for the violation,
the value that the contract applies to, and
a message indicating the kind of violation.
The procedure then
returns a string that is put into the contract error
message. Note that the value is often already included in
the message that indicates the violation.
Examples: |
| > (current-blame-format show-blame-error) | | > (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: expected <integer?>, given: 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 |
|
7.6.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.
These properties attach a contract value to the protected structure,
chaperone, or impersonator value. The function
has-contract?
returns
#t for values that have one of these properties, and
value-contract extracts the contract value.
A contract property specifies the behavior of a structure when used as
a contract. It is specified in terms of five accessors: 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-projection, which
produces a blame-tracking projection defining the behavior of the contract;
stronger, which is a predicate that determines whether this contract
(passed in the first argument) is stronger than some other contract (passed in the second argument);
and generator, which makes a random value that matches the contract,
given a size bound and an environment from which to draw interesting values.
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.
7.6.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.
'racket/contract:negative-position : symbol? 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.
'racket/contract:positive-position : symbol? 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.
'racket/contract:contract-on-boundary : symbol? 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.)
'racket/contract:internal-contract : symbol?
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
acceess to values under contract via the dependency.
(define/final-prop header body ...) |
|
header | | = | | main-id | | | | | | (main-id id ...) | | | | | | (main-id id ... . id) |
|
The same as
(define header body ...), except that uses of
main-id in the header are annotated
with the
'racket/contract:contract property
(as above).
The same as
(define header body ...), except that uses of
main-id in the header are annotated
with the
'racket/contract:contract property
(as above) and arguments are annotated with the
'racket/contract:positive-position property.
7.6.4 Utilities for Building New Combinators
Returns #t if the contract x accepts either fewer
or the same number of values as y does.
This function is conservative, so it may return #f when
x does, in fact, accept fewer values.
Returns a boolean indicating whether the first-order tests
of contract pass for v.
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.
Produces the first-order test used by
or/c to match values to
higher-order contracts.