8.3 Parametric Contracts
(require racket/contract/parametric) | package: base |
The most convenient way to use parametric contract is to use contract-out’s #:exists keyword. The racket/contract/parametric provides a few more, general-purpose parametric contracts.
syntax
(parametric->/c (x ...) c)
At each application of a function, the parametric->/c contract constructs a new opaque wrapper for each x; values flowing into the polymorphic function (i.e. values protected by some x in negative position with respect to parametric->/c) are wrapped in the corresponding opaque wrapper. Values flowing out of the polymorphic function (i.e. values protected by some x in positive position with respect to parametric->/c) are checked for the appropriate wrapper. If they have it, they are unwrapped; if they do not, a contract violation is signaled.
> (define swap-ctc (parametric->/c [A B] (-> A B (values B A))))
> (define/contract (good-swap a b) swap-ctc (values b a)) > (good-swap 1 2)
2
1
> (define/contract (bad-swap a b) swap-ctc (values a b)) > (bad-swap 1 2) bad-swap: broke its own contract
promised: B
produced: #<A>
in: the range of
(parametric->/c (A B) (-> A B (values B A)))
contract from: (function bad-swap)
blaming: (function bad-swap)
(assuming the contract is correct)
at: eval:5:0
> (define/contract (copy-first a b) swap-ctc (values a a)) > (let ((v 'same-symbol)) (copy-first v v)) copy-first: broke its own contract
promised: B
produced: #<A>
in: the range of
(parametric->/c (A B) (-> A B (values B A)))
contract from: (function copy-first)
blaming: (function copy-first)
(assuming the contract is correct)
at: eval:7:0
> (define/contract (inspect-first a b) swap-ctc (if (integer? a) (+ a b) (raise-user-error "an opaque wrapped value is not an integer"))) > (inspect-first 1 2) an opaque wrapped value is not an integer
Universal contracts accept all values when in negative positions (e.g., function inputs) and wrap them in an opaque struct, hiding the precise value. In positive positions (e.g. function returns), a universal contract accepts only values that were previously accepted in negative positions (by checking for the wrappers).
The name is used to identify the contract in error messages and defaults to a name based on the lexical context of new-∀/c.
The new-∀/c contract constructor is dual to new-∃/c.
Existential contracts accept all values when in positive positions (e.g., function returns) and wrap them in an opaque struct, hiding the precise value. In negative positions (e.g. function inputs), they accepts only values that were previously accepted in positive positions (by checking for the wrappers).
The name is used to identify the contract in error messages and defaults to a name based on the lexical context of new-∀/c.