8.2 Function Contracts
A function contract wraps a procedure to delay
checks for its arguments and results. There are three
primary function contract combinators that have increasing
amounts of expressiveness and increasing additional
overheads. The first -> is the cheapest. It
generates wrapper functions that can call the original
function directly. Contracts built with ->* require
packaging up arguments as lists in the wrapper function and
then using either keyword-apply or
apply. Finally, ->i
is the most expensive (along with ->d),
because it requires delaying the evaluation of the contract
expressions for the domain and range until the function
itself is called or returns.
The case-> contract is a specialized contract,
designed to match case-lambda and
unconstrained-domain-> allows range checking
without requiring that the domain have any particular shape
(see below for an example use).
(-> dom ... range)
|
(-> dom ... ellipsis dom-expr ... range) |
|
dom | | = | | dom-expr | | | | | | keyword dom-expr | | | | | | range | | = | | range-expr | | | | | | (values range-expr ...) | | | | | | any | | | | | | ellipsis | | = | | ... |
|
Produces a contract for a function that accepts the argument
specified by the
dom-expr contracts and returns
either a fixed number of
results or completely unspecified results (the latter when
any is specified).
Each dom-expr is a contract on an argument to a
function, and each range-expr is a contract on a
result of the function.
If the domain contain ...
then the function accepts as many arguments as the rest of
the contracts in the domain portion specify, as well as
arbitrarily many more that match the contract just before the
.... Otherwise, the contract accepts exactly the
argument specified.
Using a -> between two whitespace-delimited
.s is the same as putting the -> right
after the enclosing opening parenthesis. See
Lists and Racket Syntax or Reading Pairs and Lists for more
information.
For example,
produces a contract on functions of two arguments. The first argument
must be an integer, and the second argument must be a boolean. The
function must produce an integer.
A domain specification may include a keyword. If so, the function must
accept corresponding (mandatory) keyword arguments, and the values for
the keyword arguments must match the corresponding contracts. For
example:
is a contract on a function that accepts a by-position argument that
is an integer and a #:x argument that is a boolean.
As an example that uses an
..., this contract:
on a function insists that the first and last arguments to
the function must be integers (and there must be at least
two arguments) and any other arguments must be strings.
If any is used as the last sub-form for ->, no
contract checking is performed on the result of the function, and
thus any number of values is legal (even different numbers on different
invocations of the function).
If (values range-expr ...) is used as the last sub-form of
->, the function must produce a result for each contract, and
each value must match its respective contract.
Changed in version 6.4.0.5 of package base: Added support for ellipses
(->* (mandatory-dom ...) optional-doms rest pre range post)
|
|
mandatory-dom | | = | | dom-expr | | | | | | keyword dom-expr | | | | | | optional-doms | | = | | | | | | | | (optional-dom ...) | | | | | | optional-dom | | = | | dom-expr | | | | | | keyword dom-expr | | | | | | rest | | = | | | | | | | | #:rest rest-expr | | | | | | pre | | = | | | | | | | | #:pre pre-cond-expr | | | | | | #:pre/desc pre-cond-expr | | | | | | range | | = | | range-expr | | | | | | (values range-expr ...) | | | | | | any | | | | | | post | | = | | | | | | | | #:post post-cond-expr | | | | | | #:post/desc post-cond-expr |
|
The
->* contract combinator produces contracts for functions
that accept optional arguments (either keyword or positional) and/or
arbitrarily many arguments. The first clause of a
->*
contract describes the mandatory arguments, and is similar to the
argument description of a
-> contract. The second clause
describes the optional arguments. The range of description can either
be
any or a sequence of contracts, indicating that the
function must return multiple values.
If present, the
rest-expr contract governs the arguments in the rest
parameter. Note that the rest-expr contract governs only
the arguments in the rest parameter, not those in mandatory arguments.
For example, this contract:
does not match the function
because the contract insists that the function accept zero arguments
(because there are no mandatory arguments listed in the contract). The
->* contract does not know that the contract on the rest argument is
going to end up disallowing empty argument lists.
The pre-cond-expr and post-cond-expr
expressions are checked as the function is called and returns,
respectively, and allow checking of the environment without an
explicit connection to an argument (or a result). If the #:pre
or #:post keywords are used, then a #f result is
treated as a failure and any other result is treated as success.
If the #:pre/desc or #:post/desc keyword is used,
the result of the expression must be either a boolean, a string, or a
list of strings, where #t means success and any of the other
results mean failure. If the result is a string or a list of strings,
the strings are expected to have at exactly one space after each
newline and multiple are used as lines in the error message; the contract
itself adds single space of indentation to each of the strings in that case.
The formatting requirements are not checked but they
match the recommendations in Error Message Conventions.
As an example, the contract
matches functions that optionally accept a boolean, an
integer keyword argument #:x and arbitrarily more
symbols, and that return a symbol.
(->i maybe-chaperone | (mandatory-dependent-dom ...) | dependent-rest | pre-condition | dependent-range | post-condition) |
|
(->i maybe-chaperone | (mandatory-dependent-dom ...) | (optional-dependent-dom ...) | dependent-rest | pre-condition | dependent-range | post-condition) |
|
|
maybe-chaperone | | = | | #:chaperone | | | | | | | | | | | | mandatory-dependent-dom | | = | | id+ctc | | | | | | keyword id+ctc | | | | | | optional-dependent-dom | | = | | id+ctc | | | | | | keyword id+ctc | | | | | | dependent-rest | | = | | | | | | | | #:rest id+ctc | | | | | | pre-condition | | = | | | | | | | | #:pre (id ...) | boolean-expr pre-condition |
| | | | | | #:pre/desc (id ...) | expr pre-condition |
| | | | | | #:pre/name (id ...) | string boolean-expr pre-condition |
| | | | | | dependent-range | | = | | any | | | | | | id+ctc | | | | | | un+ctc | | | | | | (values id+ctc ...) | | | | | | (values un+ctc ...) | | | | | | post-condition | | = | | | | | | | | #:post (id ...) | boolean-expr post-condition |
| | | | | | #:post/desc (id ...) | expr post-condition |
| | | | | | #:post/name (id ...) | string boolean-expr post-condition |
| | | | | | id+ctc | | = | | [id contract-expr] | | | | | | [id (id ...) contract-expr] | | | | | | un+ctc | | = | | [_ contract-expr] | | | | | | [_ (id ...) contract-expr] |
|
The
->i contract combinator differs from the
->*
combinator in that each argument and result is named and these names can
be used in the subcontracts and in the pre-/post-condition clauses.
In other words,
->i expresses dependencies among arguments and results.
The optional first keyword argument to ->i indicates if the result
contract will be a chaperone. If it is #:chaperone, all of the contract for the arguments
and results must be chaperone contracts and the result of ->i will be
a chaperone contract. If it is not present, then the result
contract will not be a chaperone contract.
The first sub-form of a ->i contract covers the mandatory and the
second sub-form covers the optional arguments. Following that is an optional
rest-args contract, and an optional pre-condition. The pre-condition is
introduced with the #:pre keyword followed by the list of names on
which it depends. If the #:pre/name keyword is used, the string
supplied is used as part of the error message; similarly with #:post/name.
If #:pre/desc or #:post/desc is used, the the result of
the expression is treated the same way as ->*.
The dependent-range non-terminal specifies the possible result
contracts. If it is any, then any value is allowed. Otherwise, the
result contract pairs a name and a contract or a multiple values return
with names and contracts. In the last two cases, the range contract may be
optionally followed by a post-condition; the post-condition expression is
not allowed if the range contract is any. Like the pre-condition,
the post-condition must specify the variables on which it depends.
Consider this sample contract:
It specifies a function of two arguments, both numbers. The contract on the
second argument (y) demands that it is greater than the first
argument. The result contract promises a number that is greater than the
sum of the two arguments. While the dependency specification for y
signals that the argument contract depends on the value of the first
argument, the dependency sequence for result indicates that the
contract depends on both argument values. In general, an
empty sequence is (nearly) equivalent to not adding
a sequence at all except that the former is more expensive than the latter.
Since the contract for x does not depend on anything else, it does
not come with any dependency sequence, not even ().
This example is like the previous one, except the x and y
arguments are now optional keyword arguments, instead of mandatory, by-position
arguments:
The contract expressions are not always evaluated in
order. First, if there is no dependency for a given contract expression,
the contract expression is evaluated at the time that the ->i
expression is evaluated rather than the time when the function is called or
returns. These dependency-free contract expressions are evaluated in the
order in which they are listed.
Second, the dependent contract sub-expressions are evaluated when the
contracted function is called or returns in some order that satisfies the
dependencies. That is, if a contract for an argument depends on the value
of some other contract, the former is evaluated first (so that the
argument, with its contract checked, is available for the other). When
there is no dependency between two arguments (or the result and an
argument), then the contract that appears earlier in the source text is
evaluated first.
If all of the identifier positions of the range
contract are _s (underscores), then the range contract expressions
are evaluated when the function is called instead of when it returns.
Otherwise, dependent range expressions are evaluated when the function
returns.
If there are optional arguments that are not supplied, then
the corresponding variables will be bound to a special value
called
the-unsupplied-arg value. For example, in
this contract:
the contract on
x depends on
y, but
y might not be supplied at the call site. In that
case, the value of
y in the contract on
x is
the-unsupplied-arg
and the
->i contract must check for it and tailor
the contract on
x to
account for
y not being supplied.
When the contract expressions for unsupplied arguments are dependent,
and the argument is not supplied at the call site, the contract
expressions are not evaluated at all. For example, in this contract,
y’s contract expression is evaluated only when y
is supplied:
In contrast,
x’s expression is always evaluated (indeed,
it is evaluated when the
->i expression is evaluated because
it does not have any dependencies).
(->d (mandatory-dependent-dom ...) | dependent-rest | pre-condition | dependent-range | post-condition) |
|
(->d (mandatory-dependent-dom ...) | (optional-dependent-dom ...) | dependent-rest | pre-condition | dependent-range | post-condition) |
|
|
mandatory-dependent-dom | | = | | [id dom-expr] | | | | | | keyword [id dom-expr] | | | | | | optional-dependent-dom | | = | | [id dom-expr] | | | | | | keyword [id dom-expr] | | | | | | dependent-rest | | = | | | | | | | | #:rest id rest-expr | | | | | | pre-condition | | = | | | | | | | | #:pre boolean-expr | | | | | | #:pre-cond boolean-expr | | | | | | dependent-range | | = | | any | | | | | | [_ range-expr] | | | | | | (values [_ range-expr] ...) | | | | | | [id range-expr] | | | | | | (values [id range-expr] ...) | | | | | | post-condition | | = | | | | | | | | #:post-cond boolean-expr |
|
This contract is here for backwards compatibility; any new code should
use
->i instead.
This contract is similar to
->i, but is “lax”, meaning
that it does not enforce contracts internally. For example, using
this contract
will allow f to be called with #f, trigger whatever bad
behavior the author of f was trying to prohibit by insisting that
f’s contract accept only integers.
The #:pre-cond and #:post-cond keywords are aliases for
#:pre and #:post and are provided for backwards compatibility.
(case-> (-> dom-expr ... rest range) ...)
|
|
rest | | = | | | | | | | | #:rest rest-expr | | | | | | range | | = | | range-expr | | | | | | (values range-expr ...) | | | | | | any |
|
This contract form is designed to match
case-lambda. Each argument to
case-> is a
contract that governs a clause in the
case-lambda. If the
#:rest keyword is
present, the corresponding clause must accept an arbitrary
number of arguments. The
range specification is
just like that for
-> and
->*.
For example, this contract matches a function with two
cases, one that accepts an integer, returning void, and one
that accepts no arguments and returns an integer.
Such a contract could be used to guard a function that controls
access to a single shared integer.
(dynamic->* | | | [ | #:mandatory-domain-contracts mandatory-domain-contracts | | | | #:optional-domain-contracts optional-domain-contracts | | | | #:mandatory-keywords mandatory-keywords | | | | #:mandatory-keyword-contracts mandatory-keyword-contracts | | | | #:optional-keywords optional-keywords | | | | #:optional-keyword-contracts optional-keyword-contracts | | | | #:rest-contract rest-contract] | | | | #:range-contracts range-contracts) | |
|
→ contract? |
mandatory-domain-contracts : (listof contract?) = '() |
optional-domain-contracts : (listof contract?) = '() |
mandatory-keywords : (listof keyword?) = '() |
mandatory-keyword-contracts : (listof contract?) = '() |
optional-keywords : (listof keyword?) = '() |
optional-keyword-contracts : (listof contract?) = '() |
rest-contract : (or/c #f contract?) = #f |
range-contracts : (or/c #f (listof contract?)) |
Like
->*, except the number of arguments and results can be computed
at runtime, instead of being fixed at compile-time. Passing
#f as the
#:range-contracts argument produces a contract like one where
any
is used with
-> or
->*.
For many uses, dynamic->*’s result is slower than ->* (or ->),
but for some it has comparable speed. The name of the contract returned by
dynamic->* uses the -> or ->* syntax.
Constructs a contract that accepts a function, but makes no constraint
on the function’s domain. The range-exprs determine the number
of results and the contract for each result.
Generally, 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, the contract
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, the following is a definition of f that cannot
be blamed using the above contract:
Use this contract to indicate that some function
is a predicate. It is semantically equivalent to
(-> any/c boolean?).
This contract also includes an optimization so that functions returning
#t from struct-predicate-procedure? are just returned directly, without
being wrapped. This contract is used by provide/contract’s
struct sub-form so that struct predicates end up not being wrapped.
Used by
->i (and
->d) to bind
optional arguments that are not supplied by a call site.