► |  | Reference: PLT Scheme

1 | Language Model
2 | Syntactic Forms
3 | Datatypes
4 | Structures
5 | Classes and Objects
6 | Units
7 | Contracts
8 | Pattern Matching
9 | Control Flow
10 | Concurrency
11 | Macros
12 | Input and Output
13 | Reflection and Security
14 | Operating System
15 | Memory Management
16 | Unsafe Operations
17 | Running PLT Scheme
| Bibliography
| Index

▼ | 7 | Contracts

7.1 | Data-structure Contracts
7.2 | Function Contracts
7.3 | Lazy Data-structure Contracts
7.4 | Attaching Contracts to Values
7.5 | Building New Contract Combinators
7.6 | Contract Utilities

► | 7.5 | Building New Contract Combinators

7.5.1 | Contracts as structs

On this page:

make-proj-contract
build-compound-type-name
coerce-contract
coerce-contracts
coerce-flat-contract
coerce-flat-contracts
coerce-contract/ f
raise-contract-error
7.5.1`` Contracts as structs
proj-prop
proj-pred?
proj-get
name-prop
name-pred?
name-get
stronger-prop
stronger-pred?
stronger-get
flat-prop
flat-pred?
flat-get
first-order-prop
first-order-pred?
first-order-get

top← prev up next →

#### 7.5`` Building New Contract Combinators

Note: The interface in this section is unstable and subject to change.

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:

(define int-proj
(lambda (x)
(if (integer? x)
x
(signal-contract-violation))))

As a second example, a projection that accepts unary functions on integers looks like this:

(define int->int-proj
(lambda (f)
(if (and (procedure? f)
(procedure-arity-includes? f 1))
(lambda (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 accomodate blame, and do not provide good error messages. In order to accomodate these, contracts do not just use simple projections, but use functions that accept 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-contract-error to signal a good error message.

Here is the first of those two projections, rewritten for use in the contract system:

(define (int-proj pos neg src-info name positive-position?)
(lambda (x)
(if (integer? x)
x
(raise-contract-error
val
src-info
pos
name
"expected <integer>, given: ~e"
val))))

The first two new arguments specify who is to be blamed for positive and negative contract violations, respectively.

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 argument can ever accrue blame (and thus only pos is passed to raise-contract-error).

Compare that to the projection for our function contract:

(define (int->int-proj pos neg src-info name positive-position?)
(let ([dom (int-proj neg pos src-info
name (not positive-position?))]
[rng (int-proj pos neg src-info
name positive-position?)])
(lambda (f)
(if (and (procedure? f)
(procedure-arity-includes? f 1))
(lambda (x)
(rng (f (dom x))))
(raise-contract-error
val
src-info
pos
name
"expected a procedure of one argument, given: ~e"
val)))))

In this case, the only explicit blame covers the situation where either a non-procedure is supplied to the contract, or where 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-contract-error gets pos and not neg as its argument.

The checking for the domain and range are delegated to the int-proj function, which is supplied its arguments in the first two line 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 reverse the order of the pos and neg arguments so that the positive becomes the negative.

This is not just a cheap trick to get this 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 travelling 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, 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.

(define (make-simple-function-contract dom-proj range-proj)
(lambda (pos neg src-info name positive-position?)
(let ([dom (dom-proj neg pos src-info
name (not positive-position?))]
[rng (range-proj pos neg src-info
name positive-position?)])
(lambda (f)
(if (and (procedure? f)
(procedure-arity-includes? f 1))
(lambda (x)
(rng (f (dom x))))
(raise-contract-error
val
src-info
pos
name
"expected a procedure of one argument, given: ~e"
val))))))

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.

(make-proj-contract |  | name |  |  |  |
|  | proj |  |  |  |
|  | first-order-test) |  | → |  | contract?

name : any/c

proj

:

(or/c (-> symbol? symbol? any/c any/c any/c)
(-> symbol? symbol? any/c any/c boolean? any/c))

first-order-test : (-> any/c any/c)

Builds a new contract.

The first argument is the name of the contract. It can be an arbitrary S-expression. The second is a projection (see above).

If the projection only takes four arguments, then the positive position boolean is not passed to it (this is for backwards compatibility).

The final argument is a predicate that is a conservative, first-order test of a value. It should be a function that accepts one argument and returns a boolean. If it returns #f, its argument must be guaranteed to fail the contract, and the contract should detect this right when the projection is invoked. If it returns true, the value may or may not violate the contract, but any violations must not be signaled immediately.

This function is a convenience function, implemented using proj-prop, name-prop, first-order-prop, and stronger-prop. Consider using those directly (as well as flat-prop as necessary), as they allow more flexibility and generally produce more efficient contracts.

(build-compound-type-name c/s ...) → any
c/s : any/c

Produces an S-expression to be used as a name for a contract. The arguments should be either contracts or symbols. It wraps parenthesis around its arguments and extracts the names from any contracts it is supplied with.

(coerce-contract id x) → contract?
id : symbol?
x : any/c

Converts a regular scheme value into an instance of a contract struct, converting it according to the description of contracts.

If x is not one of the coercable values, coerce-contract signals an error, using the first argument in the error message.

(coerce-contracts id xs) → (listof contract?)
id : symbol?
xs : (listof any/c)

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.

(coerce-flat-contract id x) → flat-contract?
id : symbol?
x : any/c

Like coerce-contract, but requires the result to be a flat contract, not an arbitrary contract.

(coerce-flat-contracts id x) → (listof/c flat-contract?)
id : symbol?
x : (listof any/c)

Like coerce-contracts, but requires the results to be flat contracts, not arbitrary contracts.

(coerce-contract/f x) → (or/c contract? #f)
x : any/c

Like coerce-contract, but returns #f if the value cannot be coerced to a contract.

(raise-contract-error |  | val |  |  |  |
|  | src-info |  |  |  |
|  | to-blame |  |  |  |
|  | contract-name |  |  |  |
|  | fmt |  |  |  |
|  | arg ...) |  | → |  | any

val : any/c
src-info : any/c
to-blame : symbol?
contract-name : any/c
fmt : string?
arg : any/c

Signals a contract violation. The first argument is the value that failed to satisfy the contract. The second argument is is the src-info passed to the projection and the third should be either pos or neg (typically pos, see the beginning of this section) that was passed to the projection. The fourth argument is the contract-name that was passed to the projection and the remaining arguments are used with format to build an actual error message.

##### 7.5.1`` Contracts as structs

Note: The interface in this section is unstable and subject to change.

A contract is an arbitrary struct that has all of the struct properties (see Structure Type Properties in the reference manual) in this section (except that flat-prop is optional).

Generally speaking, the contract should be a struct with fields that specialize the contract in some way and then properties that implement all of the details of checking the contract and reporting errors, etc.

For example, an between/c contract is a struct that holds the bounds on the number and then has the properties below that inspect the bounds and take the corresponding action (the proj-prop checks the numbers, the name-prop constructs a name to print out for the contract, etc.).

proj-prop : struct-type-property?

(proj-pred? v) → boolean?
v : any/c

(proj-get v)

→

(-> proj-prop?
(-> symbol? symbol? (or/c #f syntax?) string? boolean?
(-> any/c any/c)))

v : proj-pred?

This is the workhorse property that implements the contract. The property should be bound to a function that accepts the struct and then returns a projection, as described in the docs for make-proj-contract above.

name-prop : struct-type-property?

(name-pred? v) → boolean?
v : any/c

(name-get v) → (-> name-pred? printable/c)
v : name-pred?

This property should be a function that accepts the struct and returns an s-expression representing the name of the property.

Examples:
> (write (between/c 1 10))
#<between/c>

> (let ([c (between/c 1 10)])
((name-get c) c))

(between/c 1 10)

stronger-prop : struct-type-property?

(stronger-pred? v) → boolean?
v : any/c

(stronger-get v) → (-> stronger-pred? stronger-pred? boolean?)
v : stronger-pred?

This property is used when optimizing contracts, in order to tell if some contract is stronger than another one. In some situations, if a contract that is already in place is stronger than one about to be put in place, then the new one is ignored.

flat-prop : struct-type-property?

(flat-pred? v) → boolean?
v : any/c

(flat-get v) → (-> flat-pred? (-> any/c boolean?))
v : flat-pred?

This property should only be present if the contract is a flat contract. In the case that it is a flat contract, the value of the property should be a predicate that determines if the contract holds.

Examples:
> (flat-pred? (-> integer? integer?))
#f

> (let* ([c (between/c 1 10)]
[pred ((flat-get c) c)])
(list (pred 9)
(pred 11)))

(#t #f)

first-order-prop : struct-type-property?

(first-order-pred? v) → boolean?
v : any/c

(first-order-get v)
→ (-> first-order-pred? (-> any/c boolean?))
v : proj-pred?

This property is used with or/c to determine which branch of the or/c applies. These don’t have to be precise (i.e., returning #f is always safe), but the more often a contract can honestly return #t, the more often it will work with or/c.

For example, function contracts typically check arity in their first-order-props.

top← prev up next →