© University of Kent - Contact | Feedback | Legal | FOI | Cookies
Practical Lazy Typed Contracts for Functional Programming
In programs contracts express properties of run-time values that the programmer expects to hold.
A simple example is asserting that an expression is a natural number, that is, a non-negative integer. A more complex example is asserting for a function that computes a clausal normal form of a formula in propositional logic that its input must be in conjunctive normal form and right-bracketed, and then its output will be a list of list of literals.
Contracts both document and enforce properties that are beyond the standard static type system (or would require awkward encoding in the type system). For functional values both pre- and post-conditions are given.
In a higher-order language contracts should also be higher-order, that is, express properties of higher-order functions that e.g. restrict arguments of a functional type. In a typed language contracts have to be typed and should be type directed, that is, for every type constructor there exists a corresponding contract combinator that has contracts for the type constructor arguments as arguments. The function type contract combinator which takes contracts for pre- and post-conditions as arguments is an example.
Adding contracts to a program leaves its meaning unchanged, unless a contract is violated and thus an exception raised. To ensure this meaning preservation in the presence of lazy evaluation, where expressions may be evaluted only partially, the expressiveness of contracts needs to be limited carefully. For algebraic data types contracts basically provide structural subtyping.
Contracts are similar to assertions. Additionally, contracts are explicitly made between several partners (at least a server, i.e., code providing some service, and a client, i.e., a user of the service) and if a contract is violated one of the partners is to blame.
The Contract library for Haskell
The library uses pure Haskell plus Template Haskell to derive data-type-dependent code and include source locations.
Some example uses:
nat :: (Integral a, Flat a) => Contract a nat = prop (>= 0) nats :: (Integral a, Flat a) => Contract [a] nats = list nat t3 :: [Integer] t3 = $assert nats [10,9..(-2)]
Here contracts for natural numbers and lists of natural numbers are defined. The function $assert takes a contract and monitors it for its second argument. Evaluating t3 yields[10,9,8,7,6,5,4,3,2,1,0,*** Exception: Contract at ContractTest.hs:22:6 failed at ..({-1}:_).. The server is to blame.
So the -1 in the list violates the contract.
sqrtC :: Float -> Float sqrtC = $attach sqrt (prop (>=0) >-> prop (>= 0)) t14 = sqrtC 4 t15 = sqrtC (-4)
Note that $attach does the same as $assert, but has its two arguments in the opposite order. Here evaluating t14 just returns 2, but evaluating t15 yields*** Exception: Contract at ContractTest.hs:56:9 failed at ({-4.0}->_) The client is to blame.
-
We can express contracts for user-defined algebraic data types as follows. Here we define a type for propositional formulae and functions that use it.
data Formula = Imp Formula Formula | And Formula Formula | Or Formula Formula | Not Formula | Atom Char $(deriveContracts ’’Formula)
The last line derives contract combinators for all data constructors of the type Formula.conjNF = pAnd conjNF conjNF |> disj disj = pOr disj disj |> lit lit = pNot atom |> atom atom = pAtom true right = pImp (right & pNotImp) right |> pAnd (right & pNotAnd) right |> pOr (right & pNotOr) right |> pNot right |> pAtom true
Here several contracts have been defined that we use below. The contract definitions look similar to the definitions of algebraic data types, but also allow the use of conjunction.clausalNF = $assert (conjNF&right >-> list (listlit)) clausalNF’ clausalNF’ :: Formula -> [[Formula]] clausalNF’ (And f1 f2) = clause f1 : clausalNF’ f2 clausalNF’ f = [clause f] clause = $assert (disj&right >-> list lit) clause’ clause’ :: Formula -> [Formula] clause’ (Or f1 f2) = f1 : clause’ f2 clause’ lit = [lit]
Using the Contract library
- Download Contract library from Hackage
Note that the library uses Template Haskell and hence requires a recent Glasgow Haskell Compiler. - Modules for demonstrating use and implementation of the library
- Further explanations in Olaf Chitil: Practical Typed Lazy Contracts, ICFP 2012, ACM.
Summary of the Contract library
- The contract combinators have simple parametrically polymorphic types, such that adding a contract does not change the type of a function.
- The library provides lazy contract combinators. Adding contracts leaves the semantics of a program unchanged, unless a contract fails.
- The library is written in pure, portable Haskell, without any use of side-effecting primitives such as unsafePerformIO that could change the semantics of the programming language.
- All data-type-dependent code is simple and thus easy to write by hand if necessary.
- The contract combinators have a nice algebra of properties. Contract assertions are partial identities and we claim that they are idempotent too. Thus contracts are projections, like eager contracts.
- If a contract is violated the raised exception does not simply blame the function or its caller, but provides additional information about the specific value that caused the violation.
- The library uses Template Haskell to derive all data-type-dependent code and include source code locations. Thus the programmer can formulate contracts for new algebraic data types without any additional work.