6 Contracts
The contract system helps guard parts of a program against each other by enforcing properties of function and method parameters and results, structure and class fields, and variables. In particular, contracts specify properties of values that are checked at various points in the program.
A contract may be flat, in which case it is checked immediately against a value. Or a contract may by higher-order, in which case it may have to wrap a value to perform further checking in the future.
A number of DSSL2 values may be used as contracts, including:
Booleans, which allow only themselves.
Characters, which allow only themselves.
Numbers, which allow only themselves.
Strings, which allow only themselves.
Functions of one argument, which are treated as flat contracts by applying as predicates.
Contracts created using the contract combinators such as OrC and FunC described below.
For example, to ensure that a function takes a string and returns a number or False, we could use the contract FunC[str?, OrC(num?, False)].
6.1 Contract syntax
compound
Binds variable var_name to the value of expression ⟨expr⟩, while applying the contract ⟨ctc⟩. Subsequent assignments to the variable also must satisfy the contract. For example:
let x : int? = 0
Note that the ⟨exprrhs⟩ is optional, and the contract will not be checked before the variable is assigned:
let x : int? x = 5
compound
def namef(name1: ⟨ctc1⟩, ..., namek: ⟨ctck⟩) -> ⟨ctcres⟩: ⟨block⟩
Defines function namef while specifying contract expressions ⟨ctc1⟩ through ⟨ctck⟩ for the parameters, and contract expression ⟨ctcres⟩ for the result. For example:
def pythag(x: num?, y: num?) -> num?: (x * x + y * y).sqrt()
Each of the contract positions is optional, and if omitted defaults to AnyC.
Optionally, after namef and before the left parenthesis, ⟨opt_cvars⟩ may appear: one or more comma-separated names, enclosed in square brackets. These are optional contract parameters to the function, which can appear in the contracts on the rest of the parameters, the result, and anywhere in the body of the function.
For example, a vector mapping function might be defined with contract parameters and contracts on its parameters as follows:
def vec_map[T, U](f: FunC[T, U], v: VecC[T]) -> VecC[U]: [ f(e) for e in v ]
When calling vec_map, it’s possible to supply actual contracts for T and U in square brackets; or omitting the square brackets, the contracts both default to AnyC.
compound
struct name:
let field_name1: ⟨ctc1⟩
...
let field_namek: ⟨ctck⟩
Defines a structure name with the given contracts ⟨ctci⟩ applied to the fields field_namei. This means that the contracts will be applied both when constructing the structure and when mutating it. For example:
struct posn: let x: float? let y: float?
Now constructing a posn will require both parameters to satisfy the float? predicate, as will assigning to either field.
It’s possible to include contracts on some fields without including them on all, and the fields with omitted contracts default to AnyC.
compound
class name [ [ { cvar },* ] ] [ ( { interface_name },* ) ]:
let field_name1: ⟨ctcfield_1⟩
...
let field_namek: ⟨ctcfield_k⟩
def meth_name0(self0 { , arg_name0: ⟨ctcarg_0⟩ }*) -> ⟨ctcres_0⟩: ⟨block0⟩
...
def meth_namen(selfn { , arg_namen: ⟨ctcarg_n⟩ }*) -> ⟨ctcres_n⟩: ⟨blockn⟩
Defines a class with contracts. See class for the basics of classes.
Contracts may be placed on:
fields, which are checked when the field is assigned,
non-self method parameters, which are checked when the method is invoked, and
method results, which are checked when the method returns.
Any contracts may be omitted, and default to AnyC. No contract may be placed on a method’s self parameter, as that parameter is already known to be an instance of the class.
If the class implements interfaces that have contracts, the interfaces’ contracts have no effect on the defined class.
A class may have some number of generic contract parameters, cvar. These can be used to parameterize a class over other contracts. When provided, they are in scope throughout the class. The external constructor receives the actual contracts for an instance of the class as optional, square bracket parameters, giving before the ordinary parameters that are passed to the internal constructor.
For example, we can define a generic position class:
class Posn[T]: let _x: T let _y: T def __init__(self, x: T, y: T): self._x = x self._y = y def x(self) -> T: self._x def y(self) -> T: self._y
Now it is possible to make a position with int? coordinates or a position with float? coordinates, by passing the coordinate contract in square brackets to the Posn constructor:
let p = Posn[int?](3, 4) let q = Posn[float?](3.0, 4.0)
Specifying contract parameters is optional, and if omitted, they default to AnyC. So we can write
let r = Posn(3, 4.0)
to get a Posn[AnyC].
When a contract parameter is given, the Posn constructor and methods all check the given values against the given contract.
When a class has generic contract parameters, its predicate can also be instantiated with contracts, using square brackets:
assert Posn?[int?](p) assert not Posn?[int?](q) assert not Posn?[int?](r) assert not Posn?[float?](p) assert Posn?[float?](q) assert not Posn?[float?](r)
If the predicate is not instantiated, it recognizes all objects of that class regardless of how their contract parameters are instantiated:
assert Posn?(p) assert Posn?(q) assert Posn?(r)
Note that a predicate not being instantated is different from instantiating it with AnyC:
assert not Posn?[AnyC](p) assert not Posn?[AnyC](q) assert Posn?[AnyC](r)
compound
interface name [ [ { cvar },* ] ]:
def meth_name1(self1 { , arg_name1: ⟨ctcarg_1⟩ }*) -> ⟨ctcres_1⟩
...
def meth_namek(selfn { , arg_namek: ⟨ctcarg_n⟩ }*) -> ⟨ctcres_k⟩
Defines a interface with contracts. See interface for the basics of interfaces.
As with a class, contracts may be provided on method parameters (except for self) and results. The contracts have no effect when a class implements the interface, but do have an effect with the interface is used to protect an instance of a class that implements it.
Defining an interface name binds three identifiers: name, name?, and name!. The first of these is used in class definitions to refer to the interface; the second is the predicate, which recognizes instances of classes that implement the interface. The third is the interface contract, which can be used to protect instances of classes that implement the interface, to ensure that their usage is only via the interface.
When an interface contract protects an object, first it checks the class of the object. If the class is not declared to implement the interface, an error is signaled. If the class does implement the interface, then a protected object is created. The protected object is like the original object, except that only the methods of the interface are usable; all other methods will error, blaming the caller, if called. Furthermore, any contracts specified on methods in the interface are applied to those methods of the protected object. (Reprotecting a protected object with the same interface has no effect.)
Here is an example of an interface with one method and a class that implements it:
interface HAS_X: def get_x(self) class Posn (HAS_X): let x let y def __init__(self, x, y): self.x = x self.y = y def get_x(self): self.x def get_y(self): self.y
We might want to ensure that some client code uses an instance of Posn only according to the HAS_X interface, without accessing its other methods. We can use the HAS_X! interface contract to protect a Posn instance in exactly this way. First, we create an object and use it normally:
let original = Posn(3, 4) assert original.get_x() == 3 assert original.get_y() == 4 assert HAS_X?(original)
Note that the interface predicate HAS_X? answers True for instances of classes that implement HAS_X.
We can protect the original object by applying the HAS_X! interface contract, as follows:
let protected: HAS_X! = original
Now, we can call protected.get_x() because interface HAS_X defines the get_x method. But we cannot call get_y on protected, because protecting it with HAS_X! makes all methods other than get_x raise an error:
assert protected.get_x() == 3 assert_error protected.get_y(), \ 'interface HAS_X is protecting method get_y'
We can still access get_y on original:
assert original.get_x() == 3 assert original.get_y() == 4
As another example of how interface contracts can protect objects against misuse, consider the following class Counter, which implements an interface STEPPABLE:
interface STEPPABLE: def step(self) class Counter (STEPPABLE): let count def __init__(self, value: int?): self.count = value def get(self): self.count def step(self): self.count = self.count + 1
The STEPPABLE! interface contract can be placed on a parameter to a function to ensure that the function only uses the given object according to the STEPPABLE interface. For example, we know that this advance function can only use the step method to advance the Counter:
def advance(counter: STEPPABLE!, count: nat?): while count > 0: counter.step() count = count - 1
A version of the function that attempts to do addition will fail because the necessary methods aren’t usable when the object is protected:
def sneaky_advance(counter: STEPPABLE!, count: nat?): # Won't work because both .__init__ and .get are missing. counter.__init__(counter.get() + count)
Like classes, interfaces can have generic contract parameters cvar. When an interface has generic contract parameters, these parameters are available to the contracts in the body of the interface. The interface contract name! takes its contract parameters as optional square bracket parameters that default to AnyC.
For example, here is a generic interface for a queue:
interface QUEUE[T]: def empty(self) -> bool? def enqueue(self, value: T) -> NoneC def dequeue(self) -> OrC(False, T)
This interface definition binds three identifiers:
Interface name QUEUE, which can be used to declare that classes implement the interface.
Predicate QUEUE?, which answers True for instances of classes that implement QUEUE, as well as QUEUE interface objects.
Generic interface contract QUEUE!, which optionally takes one square-bracket contract parameter. The resulting contract will protect an instance of any class that implements QUEUE by disabling all but the empty?, enqueue, and dequeue methods, and applying the given contracts to each. For example, QUEUE![int?], when protecting an object, ensures that its enqueue method is given an int?. Or QUEUE! can be used to protect an object, without specifying an element contract.
expr
Indexes or instantiates the result of ⟨expr0⟩.
If ⟨expr0⟩ evalutes to a vector or string, then k must equal 1, and this is an indexing expression.
If ⟨expr0⟩ is a generic function, class, or contract, then ⟨expr1⟩, ..., ⟨exprk⟩ are the parameters used to instantiate the generic function, class, or contract. For example, function contracts are created using FunC and square brackets:
FunC[int?, char?, str?]
Or here is an example of a generic pair class, which can be instantiated to particular contracts for its components using square brackets:
class Pair[T, U]: let _fst let _snd def __init__(self, fst: T, snd: U): self._fst = fst self._snd = snd def fst(self) -> T: self._fst def snd(self) -> U: self._snd let p = Pair[int?, str?](5, 'six')
6.2 Contract combinators
constant
A flat contract that accepts any value.
constant
A flat contract that accepts the value None, which is the result of pass and other statements that return no value (such as assignment and loops). This is used as the result contract for functions and methods that do not return a value.
procedure
Creates a contract that protects a vector to ensure that its elements satisfy the given contract.
For example:
let v: VecC[int?] = [2, 3, 4]
Note that vector contracts are checked lazily, when elements are indexed or assigned, rather than eagerly when first protected. So for example:
let v: VecC[int?] = [2, 3, 'four'] # okay, not checked yet assert v[1] == 3 # passes check assert_error v[2] # fails check
Assigning a non-integer to an element of v is an error as well.
procedure
Creates a contract that protects a vector of k elements to ensure that its first element matches the first given contract, its second element matches the second given contract, and so on up to the kth element and contract.
For example:
let t: VecKC[int?, str?, int?] = [2, "three", 4]
procedure
Creates a function contract with the given arguments and result. The last argument is a contract applied to the result, and all the other arguments are contracts applied to the parameters.
If the optional square bracket parameters are omitted, the resulting contract checks for a procedure, but nothing further.
procedure
Creates a contract that inverts the sense of the given flat contract.
procedure
Creates a contract that accepts a value if any of the arguments does. For the details of how this works for higher-order contracts, see racket:or/c.
procedure
Creates a contract that accepts a value if all of the arguments do. For the details of how this works for higher-order contracts, see racket:and/c.
procedure
IntInC(low: OrC(int?, False), high: OrC(int?, False)) -> contract?
Constructs a contract that accepts integers in the closed interval [low, high]. If either end of the interval is False, that end of the interval is unchecked.
procedure
apply_contract(contract?, AnyC) -> AnyC
apply_contract(contract?, AnyC, pos: str?, neg: str?) -> AnyC
Applies a contract to a value, optionally specifying the parties.
You are unlikely to need to use this.