Trace Contracts
1 Basics
trace/  c
accumulate
combine
fail
trace-contract?
fail?
2 Collector Contracts
list/  t
map/  t
collector-contract?
3 Syntax
trace-clause-macro
trace-clause-expand
trace-clause-macro?
4 Full Clause
full
trace-merge
trace?
5 Track Clause
track
suspect<%>
setof-suspect
listof-suspect
6 Attribute Contracts
make-attribute
attribute?
attribute/  c
attribute-contract?
attribute-present/  c
attribute-present?
attribute-set/  c
attribute-set!
attribute-update/  c
attribute-update!
attribute-satisfies/  c
attribute-satisfies?
8.16.0.1

Trace Contracts🔗ℹ

Cameron Moy

 (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 ...)
Binds each id to a collector contract and returns the inner contracts. When a value flows through a collector contract, clauses dependent on that collector will be notified and may raise a contract error.

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.

procedure

(fail [#:reset reset #:explain explain])  fail?

  reset : any/c = init-expr
  explain : (-> any) = (λ () ....)
Returns a value that indicates a contract failure when used as an accumulator.

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
Returns if v is a trace contract.

procedure

(fail? v)  boolean?

  v : any/c
Returns if v is a failure value.

2 Collector Contracts🔗ℹ

procedure

(list/t v ...)  collector-contract?

  v : any/c
Given a values v that include exactly one collector contract, returns a new collector that wraps the old one and constructs a list around an element before adding it to the trace.

procedure

(map/t f v ...)  collector-contract?

  f : (-> any/c any/c)
  v : any/c
Given a function f and values v that include exactly one collector contract, returns a new collector that wraps the old one and applies f before adding an element to the trace.

procedure

(collector-contract? v)  boolean?

  v : any/c
Returns if v is a collector contract.

3 Syntax🔗ℹ

These bindings are provided at phase 1.

procedure

(trace-clause-macro transformer)  trace-clause-macro?

  transformer : (-> syntax? syntax?)
Returns a syntax transformer that, when bound by define-syntax, can be used as a trace clause. The provided procedure is expected to return a valid trace/c clause as syntax.

procedure

(trace-clause-expand stx)  syntax?

  stx : syntax?
Expands the given trace clause into core form clauses (i.e. accumulate and combine).

procedure

(trace-clause-macro? v)  boolean?

  v : any/c
Returns if v is a trace clause macro.

4 Full Clause🔗ℹ

syntax

(full (id ...+) expr)

A trace clause that stores the entire history of values for each collector in a stream known as a trace. The expr must evaluate to a predicate that will be applied to all of the traces (one for each id) whenever any of them change. If the function returns #f, a contract violation is raised.

procedure

(trace-merge t ...)  trace?

  t : trace?
Returns a new trace that is the time-ordered merge of input traces t.

procedure

(trace? v)  boolean?

  v : any/c
Returns if v is a trace.

5 Track Clause🔗ℹ

syntax

(track sus-expr clause ...+)

This form acts like combine except that it tracks contract parties and causes the default error message to report suspects according to sus-expr. The sus-expr must be an object that implements suspect<%>.

interface

suspect<%> : interface?

This interface describes objects that track and report suspects. It includes two methods:
  • 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.

Reports an unordered set of suspects.

Reports a time-ordered list of suspects—one for each collected 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? = '???
Returns a fresh attribute.

procedure

(attribute? v)  boolean?

  v : any/c
Returns if v is an attribute.

procedure

(attribute/c [ctc] key val ... ...)  attribute-contract?

  ctc : flat-contract? = any/c
  key : attribute?
  val : any/c
Produces an attribute contract that associates the given attributes to the given values.

procedure

(attribute-contract? v)  boolean?

  v : any/c
Returns if v is an attribute contract.

procedure

(attribute-present/c attr)  flat-contract?

  attr : attribute?
Produces a contract that is satisfied if the protected value has the given attribute.

procedure

(attribute-present? attr e)  boolean?

  attr : attribute?
  e : any/c
Returns if e has the given attribute.

procedure

(attribute-set/c attr v)  flat-contract?

  attr : attribute?
  v : any/c
Produces a contract that sets the attribute to v.

procedure

(attribute-set! attr e v)  void?

  attr : attribute?
  e : any/c
  v : any/c
Sets the attribute on e to v.

procedure

(attribute-update/c attr f)  contract?

  attr : attribute?
  f : (-> any/c any/c)
Produces a contract that updates the attribute using f.

procedure

(attribute-update! attr e f)  void?

  attr : attribute?
  e : any/c
  f : (-> any/c any/c)
Updates the given attribute on e using f.

procedure

(attribute-satisfies/c attr f)  contract?

  attr : attribute?
  f : predicate/c
Produces a contract that is satisfied if the protected value has the attribute attr and it satisfies f.

procedure

(attribute-satisfies? attr e f)  boolean?

  attr : attribute?
  e : any/c
  f : predicate/c
Returns if e has the attribute attr and it satisfies f.