Contracts and Type Annotations
The :std/contract
package provides facilities for contract checking and type annotations.
To use the bindings from this module:
(import :std/contract)
Macros
using
(using declaration body ...)
(using (declaration ...) body ...)
declaration:
(var [expr] :~ predicate) ; contract check with predicate
(var [expr] : Type) ; contract check or cast with type
(var [expr] :- Type) ; type assertion
Type:
struct identifier
class identifier
interface identifier
The macro expands the declarations and creates a block that evaluates the body with the following effects:
- If the declaration is a predicate check with
:~
, the object identified byvar
will be checked to satisfypredicate
. If the check fails, aContractViolation
will be raised. - If the declaration is a type contract with
:
, which can be a struct, class, or interface type, then:- for structs and classes, the object identified by
var
will be predicate checked. - for interfaces, the object identified by
var
will be cast to the interface.
- for structs and classes, the object identified by
- If the declaration is a type assertion with
:-
then the relevant information will be propagated to the rest of the expansion and the compiler with an annotation. - Within the
body ...
syntactic context:- for structs, references to
var.field
will resolve to the relevant field accessor/mutator. - for classes, references to
var.slot-or-field
will resolve to the relevant slot or field accessor/mutator. - for interfaces, calls of the form
(var.method ...)
will dispatch to the relevant interface method:- If the declaration is a checked declaration with
:
, then the safe, contract checking facade procedure will be used. - If the declaration is a type assertion with
:-
, then the unchecked facade procedure will be used.
- If the declaration is a checked declaration with
- for structs, references to
- The form with the optional expression in the declaration expands to a let over using.
So
(using (var expr :~ contract) body ...)
expands to(let (var expr) (using (var :~ contract) body ...))
and so on.
Example
Here is an example from the standard library:
(defstruct lru-cache (ht hd tl size cap))
(defstruct node (key val prev next))
(def (lru-cache-ref lru key (default absent-obj))
(using (lru : lru-cache)
(cond
((hash-get lru.ht key)
=> (lambda (n)
(using (n :- node)
(lru-cache-touch! lru n)
n.val)))
((eq? default absent-obj)
(raise-unbound-key lru-cache-ref lru key))
(else default))))
(def (lru-cache-touch! lru n)
(using ((lru :- lru-cache)
(n :- node)
(hd lru.hd :- node)
(tl lru.tl :- node))
(cond
((eq? n hd))
((eq? n tl)
(using (prev n.prev :- node)
(set! prev.next #f)
(set! lru.tl prev)
(set! n.next hd)
(set! hd.prev n)
(set! n.prev #f)
(set! lru.hd n)))
(else
(using ((prev n.prev :- node)
(next n.next :- node))
(set! prev.next next)
(set! next.prev prev)
(set! n.next hd)
(set! hd.prev n)
(set! n.prev #f)
(set! lru.hd n))))))
maybe
(maybe predicate) -> lambda (o) -> bool
Macro that creates a predicate that checks that the object is either
#f
or satisfies predicate
.
in-range?
(in-range? start end) -> lambda (o) -> bool
Macro that creates a predicate that checks that
- the object satisfies
fixnum?
- the fixnum is in the
[start, end)
range, exclusive for the right bound.
in-range-inclusive?
(in-range-inclusive? start end) -> lambda (o) -> bool
Macro that creates a predicate that checks that
- the object satisfies
fixnum?
- the fixnum is in the
[start, end]
range, inclusive for the right bound.
nonnegative-fixnum?
(nonnegative-fixnum? o)
Macro version of the builtin nonnegative-fixnum?
procedure