7.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) | ||||||||||||||||||||||||||||||
|
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.
Using a -> between two whitespace-delimited .s is the same as putting the -> right after the enclosing open parenthesis. See Lists and Racket Syntax or Reading Pairs and Lists for more information.
For example,
(integer? boolean? . -> . integer?)
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:
(integer? #:x boolean? . -> . integer?)
is a contract on a function that accepts a by-position argument that is an integer and a #:x argument that is a boolean.
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.
(->* (mandatory-dom ...) optional-doms rest pre range post) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
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.
The dep-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.
The contract expressions are not 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 subexpressions 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. #; Finally, 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 and the underscore is not bound in the range, after the argument contracts are evaluated and checked. Otherwise, the 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.
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
The #:pre-cond and #:post-cond keywords are synonyms for #:pre and #:post and are provided for backwards compatibility.
(case-> (-> dom-expr ... rest range) ...) | |||||||||||||||||||||||||||||||
|
(unconstrained-domain-> range-expr ...) |
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
(provide/contract |
[f (->d ([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, the following is a definition of f that cannot be blamed using the above contract:
(define (f i g) |
(apply g (build-list i add1))) |
(unsupplied-arg? v) → boolean? |
v : any/c |