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).
syntax
(-> dom ... range)
(-> dom ... ellipsis dom-expr ... range)
dom = dom-expr | keyword dom-expr range = range-expr | (values range-expr ...) | any ellipsis = ...
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.
> (define/contract (maybe-invert i b) (-> integer? boolean? integer?) (if b (- i) i)) > (maybe-invert 1 #t) -1
> (maybe-invert #f 1) maybe-invert: contract violation
expected: integer?
given: #f
in: the 1st argument of
(-> integer? boolean? integer?)
contract from: (function maybe-invert)
blaming: top-level
(assuming the contract is correct)
at: eval:2:0
> (define/contract (maybe-invert i #:invert? b) (-> integer? #:invert? boolean? integer?) (if b (- i) i)) > (maybe-invert 1 #:invert? #t) -1
> (maybe-invert 1 #f) maybe-invert: arity mismatch;
the expected number of arguments does not match the given
number
expected: 1 plus an argument with keyword #:invert?
given: 2
arguments...:
1
#f
> (define/contract (string-length/between? lower-bound s1 . more-args) (-> integer? string? ... integer? boolean?) (define all-but-first-arg-backwards (reverse (cons s1 more-args))) (define upper-bound (first all-but-first-arg-backwards)) (define strings (rest all-but-first-arg-backwards)) (define strings-length (for/sum ([str (in-list strings)]) (string-length str))) (<= lower-bound strings-length upper-bound)) > (string-length/between? 4 "farmer" "john" 40) #t
> (string-length/between? 4 "farmer" 'john 40) string-length/between?: contract violation
expected: string?
given: 'john
in: the repeated argument of
(-> integer? string? ... integer? boolean?)
contract from:
(function string-length/between?)
blaming: top-level
(assuming the contract is correct)
at: eval:2:0
> (string-length/between? 4 "farmer" "john" "fourty") string-length/between?: contract violation
expected: integer?
given: "fourty"
in: the last argument of
(-> integer? string? ... integer? boolean?)
contract from:
(function string-length/between?)
blaming: top-level
(assuming the contract is correct)
at: eval:2:0
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).
> (define/contract (multiple-xs n x) (-> natural? any/c any) (apply values (for/list ([_ (in-range n)]) n))) > (multiple-xs 4 "four")
4
4
4
4
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.
> (define/contract (multiple-xs n x) (-> natural? any/c (values any/c any/c any/c)) (apply values (for/list ([_ (in-range n)]) n))) > (multiple-xs 3 "three")
3
3
3
> (multiple-xs 4 "four") multiple-xs: broke its own contract;
expected 3 values, returned 4 values
in: the range of
(->
natural?
any/c
(values any/c any/c any/c))
contract from: (function multiple-xs)
blaming: (function multiple-xs)
(assuming the contract is correct)
at: eval:2:0
Changed in version 6.4.0.5 of package base: Added support for ellipses
syntax
(->* (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
(λ (x . rest) x)
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.
syntax
(->i maybe-chaperone (mandatory-dependent-dom ...) dependent-rest pre-condition param-value dependent-range post-condition)
(->i maybe-chaperone (mandatory-dependent-dom ...) (optional-dependent-dom ...) dependent-rest pre-condition param-value 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 param-value =
|
#:param (id ...) param-expr val-expr param-value 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 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 result of the expression is treated the same way as ->*.
Following the pre-condition is the optional param-value non-terminal that specifies parameters to be assigned to during the dynamic extent of the function. Each assignment is introduced with the #:param keyword followed by the list of names on which it depends, a param-expr that determines the parameter to set, and a val-expr that will be associated with the parameter.
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.
(->i () (#:x [x number?] #:y [y (x) (>=/c x)]) [result (x y) (and/c number? (if (and (number? x) (number? y)) (>=/c (+ x y)) any/c))])
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 a range contract with a dependency 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.
Changed in version 8.7.0.1 of package base: Added #:param.
syntax
(->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
The #:pre-cond and #:post-cond keywords are aliases for #:pre and #:post and are provided for backwards compatibility.
procedure
(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?))
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.
syntax
(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-out [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)))
value
procedure
(unsupplied-arg? v) → boolean?
v : any/c