contract.ss: Contracts
To load: (require (lib "contract.ss"))
MzLib's contract.ss library defines new forms of expression that specify contracts and new forms of expression that attach contracts to values.
This section describes three classes of contracts: contracts for flat values (described in section 14.1), contracts for functions (described in section 14.2), and contracts for objects and classes (described in section 14.4).
This section also describes how to establish a contract, that is, how to indicate that a particular contract should be enforced at a particular point in the program in section 14.5, and how to make new contract combinators in section 14.6.
14.1 Flat Contracts
A contract for a flat value can be a predicate that accepts the value and returns a boolean indicating if the contract holds.
(flat-contract
predicate
)
FLAT-CONTRACT
Constructs a contract from predicate
.
(flat-named-contract
type-name predicate
)
FLAT-CONTRACT
For better error reporting, a flat
contract can be constructed with
flat-named-contract
, a procedure that accepts two
arguments. The first argument must be a string that
describes the type that the predicate checks for. The second
argument is the predicate itself.
any/c
is a flat contract that accepts any value.
If you are using this predicate as the result portion of a
function contract, consider using any
instead. It
behaves the same, but in that one restrictive context has
better memory performance.
none/c
is a flat contract that accepts no values.
or/c
accepts any number of predicates and
higher-order contracts and returns a contract that accepts
any value that any one of the contracts accepts,
individually.
If all of the arguments are predicates or flat contracts, it returns a flat contract. If only one of the arguments is a higher-order contract, it returns a contract that just checks the flat contracts and, if they don't pass, applies the higher-order contract.
If there are multiple higher-order
contracts, or/c
uses contract-first-order-passes?
to distinguish
between them. More precisely, when an or/c
is
checked, it first checks all of the flat contracts. If
none of them pass, it
calls contract-first-order-passes?
with each of
the higher-order contracts. If only one returns
true, or/c
uses that contract. If none of them
return true, it signals a contract violation. If more than
one returns true, it signals an error indicating that
the or/c
contract is malformed.
or/c
tests any values by applying the contracts
in order, from left to right, with the exception that it
always moves the non-flat contracts (if any) to the end,
checking them last.
and/c
accepts any number of contracts and returns
a contract that checks that accepts any value that satisfies all of
the contracts, simultaneously.
If all of the arguments are predicates or flat
contracts, and/c
produces a flat contract.
and/c
tests any values by applying the contracts in order,
from left to right.
(not/c
flat-contract
)
FLAT-CONTRACT
not/c
accepts a flat contracts or
a predicate and returns a flat contract that checks the
inverse of the argument.
=/c
accepts a number and returns a flat contract
that requires the input to be a number and equal to the
original input.
>=/c
accepts a number and returns a flat contract
that requires the input to be a number and greater than or
equal to the original input.
<=/c
accepts a number and returns a
flat contract that requires the input to be a number and
less than or equal to the original input.
(between/c
number number
)
FLAT-CONTRACT
between/c
accepts two numbers
and returns a flat contract that requires the input to
between the two numbers (or equal to one of them).
>/c
accepts a number and returns a flat contract
that requires the input to be a number and greater than
the original input.
</c
accepts a number and returns a flat contract
that requires the input to be a number and less than the
original input.
(integer-in
exact-integer exact-integer
)
FLAT-CONTRACT
integer-in
accepts two exact integers and returns a
flat contract that recognizes exact integers between the two
inputs, or equal to one of its inputs.
(real-in
real real
)
FLAT-CONTRACT
real-in
accepts two real numbers and returns a
flat contract that recognizes real numbers between the two
inputs, or equal to one of its inputs.
natural-number/c
FLAT-CONTRACT
natural-number/c
is a contract that recognizes
natural numbers (i.e., an integer that is either
positive or zero).
(string/len
number
)
FLAT-CONTRACT
string/len
accepts a number and returns a flat contract
that recognizes strings that have fewer than that number
of characters.
false/c
is a flat contract that recognizes #f
.
printable/c
is a flat contract that recognizes
values that can be written out and read back in
with
and write
.read
(one-of/c
value
···1)
FLAT-CONTRACT
one-of/c
accepts any number of atomic values and returns a
flat contract that recognizes those values, using
as the comparison predicate. For the purposes of eqv?
one-of/c
,
atomic values are defined to be: characters, symbols, booleans, null
keywords, numbers, void, and undefined.
(symbols
symbol
···1)
FLAT-CONTRACT
symbols
accepts any number of symbols and returns a
flat contract that recognizes those symbols.
(is-a?/c
class-or-interface
)
FLAT-CONTRACT
is-a?/c
accepts a class or
interface and returns a flat contract that recognizes if
objects are subclasses of the class or implement the
interface.
(implementation?/c
interface
)
FLAT-CONTRACT
implementation?/c
accepts an interface and returns a flat contract that
recognizes if classes are implement the given interface.
(subclass?/c
class
)
FLAT-CONTRACT
subclass?/c
accepts a class
and returns a flat-contract that recognizes classes that
are subclasses of the original class.
(vectorof
flat-contract
)
FLAT-CONTRACT
vectorof
accepts a flat contract (or a predicate
which is converted to a flat contract
via flat-contract
) and returns a predicate that
checks for vectors whose elements match the original
flat contract.
(vector-immutableof
contract
)
CONTRACT
vector-immutableof
accepts a contract (or a predicate
which is converted to a flat contract) and returns a
contract that checks for immutable lists whose elements
match the original contract. In contrast
to vectorof
, vector-immutableof
accepts
arbitrary contracts, not just flat contracts.
Beware, however, that when a value is applied to this
contract, the result will not be
to the
input.eq?
(vector/c
flat-contract
···)
FLAT-CONTRACT
vector/c
accepts any number of flat contracts (or predicates
which are converted to flat contracts
via flat-contract
) and returns a flat-contract that
recognizes vectors. The number of elements in the vector
must match the number of arguments supplied
to vector/c
and the elements of the vector must
match the corresponding flat contracts.
(vector-immutable/c
contract
···)
CONTRACT
vector-immutable/c
accepts any number of contracts (or predicates
which are converted to flat contracts
via flat-contract
) and returns a contract that
recognizes vectors. The number of elements in the vector
must match the number of arguments supplied
to vector-immutable/c
and the elements of the
vector must match the corresponding contracts.
In contrast to vector/c
, vector-immutable/c
accepts arbitrary contracts, not just flat contracts.
Beware, however, that when a value is applied to this
contract, the result will not be
to the
input.eq?
(box/c
flat-contract
)
FLAT-CONTRACT
box/c
accepts a flat contract (or predicate that
is converted to a flat contract via flat-contract
)
and returns a flat contract that recognizes for boxes whose
contents match box/c
's argument.
(box-immutable/c
contract
)
CONTRACT
box-immutable/c
one contracts (or a predicate
that is converted to a flat contract
via flat-contract
) and returns a contract that
recognizes boxes. The contents of the box must match the
contract passed to box-immutable/c
.
In contrast to box/c
, box-immutable/c
accepts an arbitrary contract, not just a flat contract.
Beware, however, that when a value is applied to this
contract, the result will not be
to the
input.eq?
(listof
flat-contract
)
FLAT-CONTRACT
listof
accepts a flat contract (or a predicate
which is converted to a flat contract) and returns a flat
contract that checks for lists whose elements match the
original flat contract.
(list-immutableof
contract
)
CONTRACT
list-immutableof
accepts a contract (or a predicate
which is converted to a flat contract) and returns a
contract that checks for immutable lists whose elements
match the original contract. In contrast
to listof
, list-immutableof
accepts
arbitrary contracts, not just flat contracts.
Beware, however, that when a value is applied to this
contract, the result will not be
to the
input.eq?
(listof-unsafe
contract
)
CONTRACT
Use this contract combinator with care.
listof-unsafe
is like listof
in that the
contracts it produces accept mutable lists and it is
like listof-immutable
in that it accepts a
an arbitrary contract as an argument.
It is unlike both in that it is unsafe, because it copies
the list. This is unsafe because it can affect the behavior
of a program whose contracts always pass. In particular,
since the contract copies the list, the behavior of programs
that use
or set-car!
might
change. We include this contract in the library because many
of Scheme's primitives produce lists and many Scheme
programs never use set-cdr!
or set-car!
on
those lists. In that case, this contract combinator is safe.set-cdr!
(cons/c
flat-contract flat-contract
)
FLAT-CONTRACT
cons/c
accepts two flat contracts (or predicates
that are converted to flat contracts
via flat-contract
) and returns a flat contract
that recognizes cons cells whose car and cdr correspond
to cons/c
's two arguments.
(cons-immutable/c
contract contract
)
CONTRACT
cons-immutable/c
accepts two contracts (or predicates
that are converted to flat contracts
via flat-contract
) and returns a contract that
recognizes immutable cons cells whose car and cdr
correspond to cons-immutable/c
's two arguments.
In contrast to cons/c
, cons-immutable/c
accepts arbitrary contracts, not just flat contracts.
Beware, however, that when a value is applied to this
contract, the result will not be
to the
input.eq?
(cons-unsafe/c
contract contract
)
CONTRACT
Use this contract combinator with care.
cons-unsafe/c
is like cons/c
in that the
contracts it produces accept mutable lists and it is
like cons-immutable/c
in that it accepts a
an arbitrary contract as an argument.
It is unlike both in that it is unsafe, because it copies
the list. This is unsafe because it can affect the behavior
of a program whose contracts always pass. In particular,
since the contract copies the list, the behavior of programs
that use
or set-car!
might
change. We include this contract in the library because many
of Scheme's primitives produce lists and many Scheme
programs never use set-cdr!
or set-car!
on
those lists. In that case, this contract combinator is safe.set-cdr!
(list/c
flat-contract
···)
FLAT-CONTRACT
list/c
accepts an arbitrary
number of flat contracts (or predicates that are converted to
flat contracts via flat-contract
) and returns a
flat contract that recognizes for lists whose length is the same as
the number of arguments to
list/c
and whose elements match those arguments.
(list-immutable/c
contract
···)
CONTRACT
list-immutable/c
accepts an arbitrary
number of contracts (or predicates that are converted to
flat contracts via flat-contract
) and returns a
contract that recognizes for lists whose length is the same
as the number of arguments to
list-immutable/c
and whose elements match those contracts.
In contrast to list/c
, list-immutable/c
accepts arbitrary contracts, not just flat contracts.
Beware, however, that when a value is applied to this
contract, the result will not be
to the
input.eq?
(list-unsafe/c
contract
···)
CONTRACT
Use this contract combinator with care.
list-unsafe/c
is like list/c
in that the
contracts it produces accept mutable lists and it is
like list-immutable/c
in that it accepts a
an arbitrary contract as an argument.
It is unlike both in that it is unsafe, because it copies
the list. This is unsafe because it can affect the behavior
of a program whose contracts always pass. In particular,
since the contract copies the list, the behavior of programs
that use
or set-car!
might
change. We include this contract in the library because many
of Scheme's primitives produce lists and many Scheme
programs never use set-cdr!
or set-car!
on
those lists. In that case, this contract combinator is safe.set-cdr!
(syntax/c
flat-contract
)
FLAT-CONTRACT
syntax/c
accepts a flat contract and produces a
flat contract that recognizes syntax objects whose
contents match the argument to syntax/c
.
(struct/c
struct-name flat-contract ...
)
FLAT-CONTRACT
struct/c
accepts a struct name and as many flat
contracts as there are fields in the named struct. It
returns a contract that accepts instances of that struct
whose fields match the given contracts.
(
CONTRACT
parameter/c
contract
)
Builds a contract on parameters, whose contents must match contract
.
(
SYNTAX
flat-rec-contract
name flat-contract ···
)
Each flat-rec-contract
form constructs a flat
recursive contract. The first argument is the name of the
contract and the following arguments are flat contract
expressions that may refer to name
.
As an example, this contract:
(flat-rec-contract sexp (cons/c sexp sexp)number?
symbol?
)
is a flat contract that checks for (a limited form of)
s-expressions. It says that an sexp
is either
two sexp
combined with
, or a number,
or a symbol.cons
Note that if the contract is applied to a circular value, contract checking will not terminate.
(
SYNTAX
flat-murec-contract
([name flat-contract ···] ···) body ···
)
The flat-murec-contract
form is a generalization
of flat-rec-contracts
for defining several
mutually recursive flat contracts simultaneously.
Each of the names is visible in the
entire flat-murec-contract
and the result of the
final body expression is the result of the entire form.
Note that if the contract is applied to a circular value, contract checking will not terminate.
14.2 Function Contracts
->
This section describes the contract constructors for function contracts. This is their shape:
contract-expr ::== | (case-> arrow-contract-expr ···) | arrow-contract-expr arrow-contract-expr ::== | (-> expr ··· expr) | (-> expr ··· any) | (-> expr ··· (values
expr ···)) | (->* (expr ···) (expr ···)) | (->* (expr ···) any) | (->* (expr ···) expr (expr ···)) | (->* (expr ···) expr any) | (->d expr ··· expr) | (->d* (expr ···) expr) | (->d* (expr ···) expr expr) | (->r ((id expr) ···) expr) | (->r ((id expr) ···) any) | (->r ((id expr) ···) (values
(id expr) ···)) | (->r ((id expr) ···) id expr expr) | (->r ((id expr) ···) id expr any) | (->r ((id expr) ···) id expr (values
(id expr) ···)) | (->pp ((id expr) ···) pre-expr expr res-id post-expr) | (->pp ((id expr) ···) pre-expr any) | (->pp ((id expr) ···) pre-expr (values
(id expr) ···) post-expr) | (->pp-rest ((id expr) ···) id expr pre-expr expr res-id post-expr) | (->pp-rest ((id expr) ···) id expr pre-expr any) | (->pp-rest ((id expr) ···) id expr pre-expr (values
(id expr) ···) post-expr) | (opt-> (expr ···) (expr ···) expr) | (opt->* (expr ···) (expr ···) any) | (opt->* (expr ···) (expr ···) (expr ···)) | (unconstrained-domain-> expr ···)
where expr
is any expression.
(
SYNTAX
->
expr ···
)
(
SYNTAX
->
expr ···any
)
The ->
contract is for functions that accept a
fixed number of arguments and return a single result. The
last argument to ->
is the contract on the result
of the function and the other arguments are the contracts on
the arguments to the function. Each of the arguments to
->
must be another contract expression or a
predicate. For example, this expression:
(integer? boolean? . -> . integer?)
is a contract on functions of two arguments. The first must
be an integer and the second a boolean and the function must
return an integer. (This example uses
MzScheme's infix notation
so that the ->
appears in a suggestive place; see section 11.2.4 in PLT MzScheme: Language Manual).
If any
is used as the last argument to ->
,
no contract checking is performed on the result of the
function, and tail-recursion is preserved. Except for the
memory performance, this is the same as using any/c
in the result.
The final case of ->
expressions
treats
as a local keyword -- that is, you
may not return multiple values to this position, instead if
the word values
syntactically appears in the in
the last argument to values
->
the function is treated as
a multiple value return.
(
SYNTAX
->*
(expr ···) (expr ···)
)
(
SYNTAX
->*
(expr ···) any
)
(
SYNTAX
->*
(expr ···) expr (expr ···)
)
(
SYNTAX
->*
(expr ···) expr any
)
The ->*
expression is for functions that return multiple
results and/or have rest arguments. If two arguments are supplied, the
first is the contracts on the arguments to the function and the second
is the contract on the results of the function. These situations are
also covered by ->
.
If three arguments are supplied, the first argument contains
the contracts on the arguments to the function (excluding
the rest argument), the second contains the contract on the
remaining arguments, which will have already been packaged
up as a list. The final argument is the contracts on the
results of the function. The final argument can
be any
which, like ->
, means that no
contract is enforced on the result of the function and
tail-recursion is preserved.
For example, a function that accepts one or more integer arguments and returns one boolean would have the contract:
(->* (integer?) (listof integer?) (boolean?))
(
SYNTAX
->d
expr ···
)
(
SYNTAX
->d*
(expr ···) expr)
)
(
SYNTAX
->d*
(expr ···) expr expr
)
The ->d
and ->d*
contract constructors are
like their d-less counterparts, except that the
result portion is a function that accepts the original
arguments to the function and returns the range contracts.
The range contract function for ->d*
must return
multiple values: one for each result of the original
function.
As an example, this is the contract for
:
sqrt
(number?
. ->d . (lambda (in) (lambda (out) (and (number?
out) (< (abs (- (* out out) in)) 0.01)))))
It says that the input must be a number and that the
difference between the square of the result and the original
number is less than 0.01
.
(
SYNTAX
->r
([id expr] ···) expr
)
The ->r
contract allows you to build a contract
where the arguments to a function may all depend on each
other and the result of the function may depend on all of
the arguments.
Each of the id
s names one of the actual arguments
to the function with the contract. Each of the names is
available to all of the other contracts. For example, to
define a function that accepts three arguments where the
second argument and the result must both be between the
first, you might write:
(->r ([xnumber?
] [y (and/c (>=/c x) (<=/c z))] [znumber?
]) (and/cnumber?
(>=/c x) (<=/c z)))
(
SYNTAX
->r
([id expr] ···) any
)
This variation on ->r
does not check anything
about the result of the function, which preserves tail recursion.
(
SYNTAX
->r
([id expr] ···) (values [id expr] ...)
)
This variation on ->r
allows multiple value return
values. The id
s for the domain are bound in all of
the expr
s, but the id
s for the range (the
ones inside
) are only bound in
the values
exprs
inside the
.values
As an example, this contract:
(->r () (values
[xnumber?
] [y (and/c (>=/c x) (<=/c z))] [znumber?
]))
matches functions that accept no arguments and that return three numeric values that are in ascending order.
(
SYNTAX
->r
([id expr] ···) id expr expr
)
(
SYNTAX
->r
([id expr] ···) id expr any
)
(
SYNTAX
->r
([id expr] ···) id expr (values [id expr] ...)
)
These three forms of the ->r
contract are just like the
previous ones, except that the functions they matches must
accept arbitrarily many arguments. The extra id
and the expr
just following it specify the
contracts on the extra arguments. The value of id
will alway be a list (of the extra arguments).
(
SYNTAX
->pp
([id expr] ···) pre-expr expr res-id post-expr
)
(
SYNTAX
->pp
([id expr] ···) pre-expr any
)
(
SYNTAX
->pp
([id expr] ···) pre-expr (values [id expr] ...) post-expr
)
(
SYNTAX
->pp-rest
([id expr] ···) id expr pre-expr expr res-id post-expr
)
(
SYNTAX
->pp-rest
([id expr] ···) id expr pre-expr any
)
(
SYNTAX
->pp-rest
([id expr] ···) id expr pre-expr (values [id expr] ...) post-expr
)
These six shapes of ->pp
match up to the six shapes
of ->r
forms explained above, with the addition
that the extra pre- and post-condition expressions must not
evaluate to #f
.
If the pre-condition evaluates to #f
, the caller is
blamed and if the post-condition expression evaluates
to #f
the function itself is blamed.
The argument variables are bound in the pre-expr
and the post-expr
and the variables in
the
result clauses are bound in
the values
post-expr
.
Additionally, the variable res-id
is bound to the
result in the first ->pp
case and in the
first ->pp-rest
case.
(
CONTRACT-CASE->
case->
arrow-contract-expr ···
)
The case->
expression constructs a contract for
case-lambda function. It's arguments must all be function
contracts, built by one of ->
, ->d
,
->*
, ->d*
, ->r
, ->pp
,
or ->pp-rest
.
(
SYNTAX
opt->
(req-contracts ···) (opt-contracts ···) res-contract)
)
(
SYNTAX
opt->*
(req-contracts ···) (opt-contracts ···) (res-contracts ···)
)
(
SYNTAX
opt->*
(req-contracts ···) (opt-contracts ···) any
)
The opt->
expression constructs a contract for an
opt-lambda
function. The first arguments are the
required parameters, the second arguments are the optional
parameters and the final argument is the result.
The req-contracts
expressions,
the opt-contracts
expressions, and
the res-contract
expressions can be any expression
that evaluates to a contract value.
Each opt->
expression expands into case->
.
The opt->*
expression constructs a contract for an
opt-lambda
function. The only difference between
opt->
and opt->*
is that multiple return
values are permitted with opt->*
and they are
specified in the last clause of an opt->*
expression. A result
of any
means any value or any number of values may
be returned, and the contract does not inhibit
tail-recursion.
(
CONTRACT
unconstrained-domain->
contract ···
)
Constructs a contract that accepts functions, but makes no constraint on their domain. Generally speaking, this contract must be combined with another contract to ensure that the domain is actually known to be able to safely call the function itself. For example, this contract
(provide/contract [f (->r ([size natural-number/c] [proc (and/c (unconstrained-domain->number?
) (lambda (p) (procedure-arity-includes?
p size)))])number?
)])
says that the function f
accepts a natural number
and a function. The domain of the function that f
accepts must include a case for size
arguments,
meaning that f
can safely supply size
arguments to its input. For example, this is a definition
of f
that cannot be blamed:
(define (f i g) (apply
g (build-list
iadd1
)))
(
CONTRACT
promise/c
contract
)
Constructs a contract on a promise. The contract does not force the promise, but when the promise is forced, the contract checks that the value meets the contract.
14.3 Lazy Data-structure Contracts
Typically, constracts on data structures can be written
using flat contracts. For example, one might write a sorted
list contract as a function that accepts a list and
traverses it, ensuring that the elements are in order. Such
contracts, however, can change the asymptotic running time
of the program, since the contract may end up exploring more
of a function's input than the function itself does. To
circumvent this problem, the
define-contract-struct
form introduces contract
combinators that are lazy that is, they only verify
the contract holds for the portion of some data structure
that is actually inspected. More precisely, a lazy data
structure contract on a struct is not checked until a
selector extracts a field of a struct.
The form
(define-contract-struct struct-name (field ...))
is like the corresponding
define-struct
, with two differences:
it does not define field mutators and it does define
two contract constructors: struct-name/c
and struct-name/dc
. The first is a procedure that
accepts as many arguments as there are fields and returns a
contract for struct values whose fields match the
arguments. The second is a syntactic form that also produces
contracts on the structs, but the contracts on later fields
may depend on the values of earlier fields. It syntax is:
(struct-name/dc field-spec ...)
where each field-spec
is one of the following two lines:
[field contract-expr] [field (field ...) contract-expr]
In each case, the first field name specifies which field the
contract applies to, and the fields must be specified in the
same order as the original define-contract-struct
.
The first case is for when the contract on
the field does not depend on the value
of any other field. The second case is for when the contract
on the field does depend on some other fields, and the
field names in middle second indicate which fields it
depends on. These dependencies can only be to fields
that come earlier in the struct.
As an example consider this module:
(module productmzscheme
(require (lib "contract.ss")) (define-contract-struct kons (hd tl)) ;; sorted-list/gt : number -> contract ;; produces a contract that accepts ;; sorted kons-lists whose elements ;; are all greater than `num'. (define (sorted-list/gt num) (or/cnull?
(kons/dc [hd (>=/c num)] [tl (hd) (sorted-list/gt hd)]))) ;; product : kons-list -> number ;; computes the product of the values ;; in the list. if the list contains ;; zero, it avoids traversing the rest ;; of the list. (define (product l) (cond [(null?
l) 1] [else (if (zero?
(kons-hd l)) 0 (* (kons-hd l) (product (kons-tl l))))])) (provide kons? make-kons kons-hd kons-tl) (provide/contract [product (-> (sorted-list/gt -inf.0)number?
)]))
It provides a single function, product
whose
contract indicates that it accepts sorted lists of numbers
and produces numbers. Using an ordinary flat contract for
sorted lists, the product function cannot avoid traversing
having its entire argument be traversed, since the contract
checker will traverse it before the function is called. As
written above, however, when the product function aborts the
traversal of the list, the contract checking also stops,
since the kons/dc
contract constructor generates a
lazy contract.
14.4 Object and Class Contracts
This section describes contracts on classes and objects. Here is the basic shape of an object contract:
contract-expr ::== ··· | (object-contract meth/field-spec ···) meth/field-spec ::== (meth-name meth-contract) | (field field-name contract-expr) meth-contract ::== (opt-> (required-contract-expr ···) (optional-contract-expr ···) any) (opt-> (required-contract-expr ···) (optional-contract-expr ···) result-contract-expr) | (opt->* (required-contract-expr ···) (optional-contract-expr ···) (result-contract-expr ···)) | (case-> meth-arrow-contract ···) | meth-arrow-contract meth-arrow-contract ::== (-> dom-contract-expr ··· rng-contract-expr) | (-> dom-contract-expr ··· (values
rng-contract-expr ···)) | (->* (dom-contract-expr ···) (rng-contract-expr ···)) | (->* (dom-contract-expr ···) rest-arg-contract-expr (rng-contract-expr ···)) | (->d dom-contract-expr ··· rng-contract-proc-expr) | (->d* (dom-contract-expr ···) rng-contract-proc-expr) | (->d* (dom-contract-expr ···) rest-contract-expr rng-contract-proc-expr) | (->r ((id expr) ···) expr) | (->r ((id expr) ···) id expr expr) | (->pp ((id expr) ···) pre-expr expr res-id post-expr) | (->pp ((id expr) ···) pre-expr any) | (->pp ((id expr) ···) pre-expr (values
(id expr) ···) post-expr) | (->pp-rest ((id expr) ···) id expr pre-expr expr res-id post-expr) | (->pp-rest ((id expr) ···) id expr pre-expr any) | (->pp-rest ((id expr) ···) id expr pre-expr (values
(id expr) ···) post-expr)
Each of the contracts for methods has the same semantics as the corresponding function contract (discussed above), but the syntax of the method contract must be written directly in the body of the object-contract (much like the way that methods in class definitions use the same syntax as regular function definitions, but cannot be arbitrary procedures).
The only exception is that the ->r
, ->pp
,
and ->pp-rest
contracts implicitly bind this
to the
object itself.
mixin-contract
is a
contract that recognizes mixins. It is a function
contract. It guarantees that the input to the function is
a class and the result of the function is a subclass of
the input.
(make-mixin-contract
class-or-interface
···)
CONTRACT
make-mixin-contract
is a function that constructs mixins contracts. It accepts
any number of classes and interfaces and returns a
function contract. The function contract guarantees that
the input to the function implements the interfaces and is
derived from the classes and that the result of the
function is a subclass of the input.
14.5 Attaching Contracts to Values
There are three special forms that attach contract
specification to values: provide/contract
,
define/contract
, and contract
.
(
SYNTAX
provide/contract
p/c-item ···
)
p/c-item is one of (struct identifier ((identifier contract-expr) ···)) (struct (identifier identifier) ((identifier contract-expr) ···)) (rename id id contract-expr) (id contract-expr)
A provide/contract
form can only appear at the
top-level of a module (see section 5 in PLT MzScheme: Language Manual). As with
provide
, each identifier is provided from the
module. In addition, clients of the module must live up to
the contract specified by contract-expr
.
The provide/contract
form treats modules as units of
blame. The module that defines the provided variable is expected to
meet the positive (co-variant) positions of the contract. Each module
that imports the provided variable must obey the negative
(contra-variant) positions of the contract.
Only uses of the contracted variable outside the module are checked. Inside the module, no contract checking occurs.
The rename
form of a provide/contract
exports the first variable (the internal name) with the name
specified by the second variable (the external name).
The struct
form of a provide/contract
clause provides a structure definition. Each field has a
contract that dictates the contents of the fields.
If the struct has a parent, the second struct
form
(above) must be used, with the first name referring to the
struct itself and the second name referring to the parent
struct. Unlike define-struct
, however, all of the
fields (and their contracts) must be listed. The contract on
the fields that the sub-struct shares with its parent are
only used in the contract for the sub-struct's maker, and
the selector or mutators for the super-struct are not
provided.
Note that the struct definition must come before the provide clause in the module's body.
(
SYNTAX
define/contract
id contract-expr init-value-expr
)
The define/contract
form attaches the contract
contract-expr
to init-value-expr
and binds
that to id
.
The define/contract
form treats individual
definitions as units of blame. The definition itself is
responsible for positive (co-variant) positions of the
contract and each reference to id
(including those
in the initial value expression) must meet the negative
positions of the contract.
Error messages with define/contract
are not as
clear as those provided by provide/contract
because
define/contract
cannot detect the name of the
definition where the reference to the defined variable
occurs. Instead, it uses the source location of the
reference to the variable as the name of that definition.
(
SYNTAX
contract
contract-expr to-protect-expr positive-blame negative-blame
)
(
SYNTAX
contract
contract-expr to-protect-expr positive-blame negative-blame contract-source
)
The contract
special form is the primitive
mechanism for attaching a contract to a value. Its purpose
is as a target for the expansion of some higher-level
contract specifying form.
The contract
expression adds the contract specified
by the first argument to the value in the second argument.
The result of a contract
expression is the result
of the to-protect-expr
expression, but with the
contract specified by contract-expr
enforced on
to-protect-expr
. The expressions
positive-blame
and negative-blame
must be
symbols indicating how to assign blame for positive and
negative positions of the contract specified by
contract-expr
. Finally, contract-source
,
if specified, indicates where the contract was assumed. It
must be a syntax object specifying the source location of
the location where the contract was assumed. If the syntax
object wraps a symbol, the symbol is used as the name of the
primitive whose contract was assumed. If
absent, it defaults to the source location of the
contract
expression.
14.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:
(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 (see below for details on its behavior).
Here is the first of those two projections, rewritten for use in the contract system:
(define (int-proj pos neg src-info contract-name) (lambda (x) (if (integer? x) x (raise-contract-error val src-info pos contract-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 contract-name) (let ([dom (int-proj neg pos src-info contract-name)] [rng (int-proj pos neg src-info contract-name)]) (lambda (f) (if (and (procedure?
f) (procedure-arity-includes?
f 1)) (lambda (x) (rng (f (dom x)))) (raise-contract-error val src-info pos contract-name "expected a procedure that accepts 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 contract-name) (let ([dom (dom-proj neg pos src-info contract-name)] [rng (range-proj pos neg src-info contract-name)]) (lambda (f) (if (and (procedure?
f) (procedure-arity-includes?
f 1)) (lambda (x) (rng (f (dom x)))) (raise-contract-error val src-info pos contract-name "expected a procedure that accepts 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
)
PROCEDURE
This is the simplest way to build a contract. It can be less efficient than using other contract constructors described below, but it is the right choice for new contract constructors or first-time contract builders.
The first argument is the name of the contract. It can be an arbitrary s-expression. The second is a projection (see above).
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 #t
,
the value may or may not violate the contract, but any
violations must not be signaled immediately.
From the example above, the predicate should accept unary functions, but reject all other values.
(build-compound-type-name
c/s
···)
PROCEDURE
This function 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
unquoted-identifier expression
)
SYNTAX
This macro evaluates its second argument and, if it is a contract, just returns it. If it is a procedure of arity one, it converts that into a contract. If it is neither, it signals an error, using the first argument in the error message. The message says that a contract or a procedure of arity one was expected.
(flat-contract/predicate?
val
)
PROCEDURE
A predicate that indicates when coerce-contract
will fail.
(raise-contract-error
val src-info to-blame contract-name fmt-string args
···)
PROCEDURE
This procedure 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
to
build an actual error message.format
14.7 Contract Utility
Extracts the name of the guilty party from an exception raised by the contract system.
The procedure contract?
returns #t
if its
argument is a contract (ie, constructed with one of the
combinators described in this section).
This predicate returns true when its argument is a contract
that has been constructed with flat-contract
(and
thus is essentially just a predicate).
(flat-contract-predicate
value
)
SELECTOR
This function extracts the predicate from a flat contract.
(contract-first-order-passes?
contract value
)
PROCEDURE
Returns a boolean indicating if the first-order tests
of contract
pass for value
.
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.
(make-none/c
sexp-name
)
PROCEDURE
Makes a contract that accepts no values, and reports the
name sexp-name
when signaling a contract violation.
(contract-violation->string
[violation-renderer
])
PROCEDURE
This is a parameter that is used when constructing a contract violation error. Its value is procedure that accepts six arguments: the value that the contract applies to, a syntax object representing the source location where the contract was established, the names of the two parties to the contract (as symbols) where the first one is the guilty one, an sexpression representing the contract, 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.
(
SYNTAX
recursive-contract
contract
)
Unfortunately, the standard contract combinators
(like ->
, etc) evaluate their arguments eagerly,
leading to either references to undefined variables or
infinite loops, while building recursive contracts.
The recursive-contract
form delays the evaluation
of its argument until the contract is checked, making
recursive contracts possible.
(
SYNTAX
opt/c
expr
)
This optimizes its argument contract expression by traversing its syntax and, for known contract combinators, fuses them into a single contract combinator that avoids as much allocation overhad as possible. The result is a contract that should behave identically to its argument, except faster (due to the less allocation).
(
SYNTAX
define-opt/c
(id id ...) expr
)
This defines a recursive contract and simultaneously
optimizes it. Semantically, it behaves just as if
the -opt/c
were not present, defining a function on
contracts (except that the body expression must return a
contract). But, it also optimizes that contract definition,
avoiding extra allocation, much like opt/c
does.
For example,
(define-contract-struct bt (val left right))
(define-opt/c (bst-between/c lo hi)
(or/c null?
(bt/c [val (between/c lo hi)]
[left (val) (bst-between/c lo val)]
[right (val) (bst-between/c val hi)])))
(define bst/c (bst-between/c -inf.0 +inf.0))
defines the bst/c
contract that checks the binary
search tree invariant. Removing the -opt/c
also
makes a binary search tree contract, but one that is
(approximately) 20 times slower.