Trace Contracts
(require trace-contract) | package: trace-contract-lib |
This package has not been officially released. Backwards compatibility is not guaranteed.
An ordinary contract is memoryless. When a flat contract blesses a value, it can’t retain any information about that value for use later. This precludes monitoring any temporal or multi-call properties.
A trace contract, by contrast, has memory. It collects values that flow through contract interposition points and accumulates them into a piece of user-defined state. This state decides whether a contract violation occurred or not.
For example, here is a contract that ensures the sequence of inputs to a function increases over time:
(define (gets-bigger-fold acc cur) (if (> cur acc) cur (fail))) (define gets-bigger/c (trace/c ([x real?]) (-> x any) (accumulate -inf.0 [(x) gets-bigger-fold])))
The gets-bigger/c trace contract collects function input values and supplies them to gets-bigger-fold. Initially, the accumulator is -inf.0. If the input is ever smaller than the accumulator, get-bigger-fold indicates a contract error by returning a failure accumulator. Otherwise, the input becomes the next accumulator.
Here’s an interaction that uses this contract:
> (define/contract (add42 x) gets-bigger/c (+ x 42)) > (add42 1) 43
> (add42 2) 44
> (add42 1) add42: contract violation
given: 1
accumulator: 2
in: the 1st argument of
the inner contract of
(trace/c
((x real?))
(-> x any)
(accumulate -inf.0 ((x) gets-bigger-fold)))
contract from: (function add42)
blaming: top-level
(assuming the contract is correct)
at: eval:3:0
1 Basics
syntax
(trace/c ([id contract-expr] ...+) maybe-option inner-contracts clause ...+)
maybe-option =
| #:global inner-contracts = contract-expr | (values contract-expr ...+) clause = (accumulate init-expr [(id ...+) expr] ...+) | (combine clause ...) | (clause-macro e ...)
The #:global option initializes the state of the accumulators at definition time. Hence, they are initialized only once. By default, accumulators are initialized at each trace-contract attachment.
syntax
(accumulate init-expr [(id ...+) folder-expr] ...+)
The initial accumulator is init-expr. Each subclause comes with a sequence of ids, specifying dependent collectors, and a folder function. Once every dependent collector for a subclause has collected a new value, the folding function is called with the accumulator and all of these values. If the return value is a fail value, then a contract violation is raised. Otherwise, the return value is the new accumulator.Suppose a subclause has two dependencies: x and y. Consider the situation where x collects 5, then x collects 13, then y collects 19. In this case, the subclause is only triggered once with 13 and 19; the first value collected by x, 5, is overwritten.
If the folder function has a mandatory #:blame keyword argument, then the blame object is supplied. This information is necessary for custom error messages.
syntax
(combine clause ...)
The union of the clauses. This is most useful for user-defined macros; see trace-clause-macro.
The #:reset value is used as the accumulator after a violation has occurred. This is only meaningful if the contract error was caught by an exception handler. By default, it is the initial accumulator of the clause.
The #:explain option is called to provide a custom error in the case of a contract violation. While raise-blame-error could be called directly, doing does not reset the accumulator appropriately.
procedure
(trace-contract? v) → boolean?
v : any/c
2 Collector Contracts
procedure
(list/t v ...) → collector-contract?
v : any/c
procedure
(collector-contract? v) → boolean?
v : any/c
3 Syntax
These bindings are provided at phase 1.
procedure
(trace-clause-macro transformer) → trace-clause-macro?
transformer : (-> syntax? syntax?)
procedure
(trace-clause-expand stx) → syntax?
stx : syntax?
procedure
(trace-clause-macro? v) → boolean?
v : any/c
4 Full Clause
syntax
(full (id ...+) expr)
procedure
(trace-merge t ...) → trace?
t : trace?
5 Track Clause
syntax
(track sus-expr clause ...+)
interface
add —
Given a blame party, returns a new instance of suspect<%> that incorporates this party. value —
Returns a datum that is printed as the suspect information.
value
value
6 Attribute Contracts
An attribute contract uses metadata associated with a value to determine if it passes the contract. Consider the following "box-like" structure:
(struct container (elem) #:mutable) (define init-attr (make-attribute 'init)) (define container/c (attribute/c container? init-attr #f)) (define initialized-container? (attribute-satisfies/c init-attr values)) (define initialize-container/c (attribute-set/c init-attr #t))
When a value flows through a container/c contract, it attaches a piece of metadata associating the attribute init-attr to #f. The flat contract initialized-container? reads this metadata and is satisfied if the value is #t. The initialize-contract/c contract sets the attribute to be #t.
Here are a few functions that make use of these contracts:
(define/contract (make-container) (-> container/c) (container #f)) (define/contract (set-container! c v) (-> initialize-container/c any/c void?) (set-container-elem! c v)) (define/contract (container-ref c) (-> initialized-container? any/c) (container-elem c))
Here is a correct call sequence:
> (define c-good (make-container)) > (set-container! c-good 42) > (container-ref c-good) 42
Here is an incorrect call sequence, where container-ref is called before the container is initialized:
> (define c-bad (make-container)) > (container-ref c-bad) container-ref: contract violation;
accumulator #f does not satisfy values
in: the 1st argument of
(->
(attribute-satisfies/c init values)
any/c)
contract from: (function container-ref)
blaming: top-level
(assuming the contract is correct)
at: eval:14:0
procedure
(make-attribute [name]) → attribute?
name : symbol? = '???
procedure
(attribute? v) → boolean?
v : any/c
procedure
(attribute/c [ctc] key val ... ...) → attribute-contract?
ctc : flat-contract? = any/c key : attribute? val : any/c
procedure
(attribute-contract? v) → boolean?
v : any/c
procedure
(attribute-present/c attr) → flat-contract?
attr : attribute?
procedure
(attribute-present? attr e) → boolean?
attr : attribute? e : any/c
procedure
(attribute-set/c attr v) → flat-contract?
attr : attribute? v : any/c
procedure
(attribute-set! attr e v) → void?
attr : attribute? e : any/c v : any/c
procedure
(attribute-update/c attr f) → contract?
attr : attribute? f : (-> any/c any/c)
procedure
(attribute-update! attr e f) → void?
attr : attribute? e : any/c f : (-> any/c any/c)
procedure
(attribute-satisfies/c attr f) → contract?
attr : attribute? f : predicate/c
procedure
(attribute-satisfies? attr e f) → boolean?
attr : attribute? e : any/c f : predicate/c