clojure-contracts0.0.1-SNAPSHOTContract programming for Clojure. dependencies
dev dependencies
| (this space intentionally left almost blank) | |||||||||||||||
This tutorial gives you a brief introduction to contract programming with clojure-contracts. It's better if you'd start a REPL and follow along. | ||||||||||||||||
Contract programming | ||||||||||||||||
Before you start, please check the following links to get some background about what the contract programming is and how can it be useful: | ||||||||||||||||
| ||||||||||||||||
| ||||||||||||||||
In short, contracts provides the same level of in-code documentation as type declarations do in static-typed languages (that's it, they describe what arguments a function takes and what it returns). The key difference between the two is that contracts are checked in runtime, giving much greater freedom and flexibility for programmer. | ||||||||||||||||
Another important aspect of contracts (which is especially relevant in the context of Clojure) is that they make debugging much easier by providing good error messages (yes, that's right, clojure-contracts allows you to get rid of those mysterious exceptions and creepy stacktraces!) | ||||||||||||||||
Once you've got the idea what this fuss is all about, return here and continue reading. | ||||||||||||||||
Using clojure-contracts | ||||||||||||||||
We'll start with namespace declaration. Note that it's idiomatic to
alias | ||||||||||||||||
(ns contracts.test.tutorial
(:use [contracts.core :only [provide-contract provide-contracts] :as c]
midje.sweet)) | ||||||||||||||||
The main form you should learn about is | ||||||||||||||||
(c/=> number? number?) | ||||||||||||||||
It returns the contract which ensures that function accepts a single argument, a number, and returns a number. | ||||||||||||||||
But what the contract actually is? How can we make something
useful with our newly created contract? Actually, the | ||||||||||||||||
(let [contract (c/=> number? number?)] (def constrained-inc (contract inc))) | ||||||||||||||||
We can check now that our | ||||||||||||||||
(fact "NullPointerExceptions suck!"
(constrained-inc 1) => 2
(inc nil) => (throws NullPointerException)
(constrained-inc nil) => (throws AssertionError
#"Expecting: <first arg> is: number?"
#"Given: nil")) | ||||||||||||||||
Here, | ||||||||||||||||
So, | ||||||||||||||||
(fact "Maybe it's valid to add numbers with string in javascript, but
certainly not in Clojure."
(let [constrained-plus ((c/=> [number? number?] number?) +)]
(constrained-plus 1 2) => 3
(+ "1" 2) => (throws ClassCastException
"java.lang.String cannot be cast to java.lang.Number")
(constrained-plus "1" 2) => (throws AssertionError
#"Expecting: <first arg> is: number?"
#"Given: \"1\""))) | ||||||||||||||||
Notice that double opening parens - we created anonymous contract and applied it to the function immediately. Syntax for several arguments follows the principle of least astonishment - you just put predicates for each arg in a vector. | ||||||||||||||||
This also means that in previous example you have used syntax sugar
- | ||||||||||||||||
Introducing 'provide-contract'. Pre and post. | ||||||||||||||||
So far we've been creating another var (or local binding) for
constrained versions of each function. Making it for the real code
would be very awkward, since usually you'll want to provide contracts
for already existing function. Fortunately, it's exactly the job
| ||||||||||||||||
(defn factorial [x] (reduce * (range x))) | ||||||||||||||||
(provide-contract factorial (c/=> (c/not neg?) pos?)) | ||||||||||||||||
One thing to note is that | ||||||||||||||||
Ok, now we're trying to execute it and... | ||||||||||||||||
(fact "What a shame!"
(factorial 1) => (throws AssertionError
#"Postcondition failed for #'contracts.test.tutorial/factorial"
#"Expecting: <result> is: pos?"
#"Given: 0")) | ||||||||||||||||
Oops! Contract system complains that function have violated its
postcondition! Of course, the implementation of | ||||||||||||||||
(defn factorial [x] (reduce * (range 1 (inc x)))) | ||||||||||||||||
(provide-contract factorial (c/=> (c/not neg?) pos?)) | ||||||||||||||||
(fact "Lesson learned."
(factorial 1) => 1
(factorial 0) => 1
(factorial -1) => (throws AssertionError
#"Expecting: <first arg> is: \(c/not neg\?\)"
#"Given: -1")) | ||||||||||||||||
Morale: | ||||||||||||||||
| ||||||||||||||||
| ||||||||||||||||
More on arguments and functions. | ||||||||||||||||
It's common for clojure functions to have multiple arities. For example, the following function returns the sum of the numbers in given collection. It can also take an optional argument f, which will be applied to every number before they get summed: | ||||||||||||||||
(defn sum ([numbers] (apply + numbers)) ([f numbers] (apply + (map f numbers)))) | ||||||||||||||||
This way, you can get a sum of either original collection or its modified variant: | ||||||||||||||||
(fact
(let [numbers [1.2 3.8 0.5]]
(sum numbers) => 5.5
(sum #(Math/round %) numbers) => 6)) | ||||||||||||||||
All right, now let's put constraints on our function. The syntax for multi-arity functions' contracts is pretty predictable: | ||||||||||||||||
(provide-contract sum
(c/=> ([(c/coll-of number?)]
[fn? (c/coll-of number?)])
number?)) | ||||||||||||||||
As you can see, we've wrapped preconditions for different arities
in a list. Just like | ||||||||||||||||
Let's make sure everything works properly: | ||||||||||||||||
(fact
(sum [1 2 3]) => 6
(sum [1 2 :boom]) => (throws AssertionError
#"Precondition failed for #'contracts.test.tutorial/sum"
#"Expecting: <first arg> is: \(c/coll-of number\?\)"
#"Given: \[1 2 :boom\]")
(sum inc [1 2 3]) => 9
(sum 1 [2 3]) => (throws AssertionError
#"Expecting: <first arg> is: fn\?"
#"Given: 1")) | ||||||||||||||||
Seems fine... but is it so? Consider the following code: | ||||||||||||||||
(fact "No! Not this again!"
(sum str [4 8 15 16 23 42]) => (throws ClassCastException
#"java.lang.String cannot be cast"
#"to java.lang.Number")) | ||||||||||||||||
We've passed incorrect function to | ||||||||||||||||
Fortunately, clojure-contracts provides you a feature called higher-order contract. It allows you to specify the constraints on a function which passed as an argument to (or returned by) your function. | ||||||||||||||||
Let's correct our contract definition: | ||||||||||||||||
(provide-contract sum
(c/=> ([(c/coll-of number?)]
[(c/=> number? number?) (c/coll-of number?)])
number?)) | ||||||||||||||||
We've replaced | ||||||||||||||||
(fact "Functional nirvana."
(sum str [4 8 15 16 23 42]) => (throws AssertionError
#"Postcondition failed"
#"Expecting: <result> is: number\?"
#"Given: \"4\"")) | ||||||||||||||||
That's much clearer: contract system tells the caller that his function is expected to return a number, but returns the string "4" instead. | ||||||||||||||||
Checking arbitrary expressions. | ||||||||||||||||
You already know pretty much about clojure-contracts, but there is one omission in your knowledge: clojure's standart mechanism of pre and post conditions doesn't limit you to checking predicates against single arguments; it allows you to make an assertion on any expression involving arguments and\or function's return value: | ||||||||||||||||
(defn foo [a b]
{:pre [(not= (+ a b) 13)]}
(comment ...)) | ||||||||||||||||
(fact "Someone very superstitious has wrote this function." (foo 7 6) => (throws AssertionError)) | ||||||||||||||||
Of course, it's possible to write this kind of contracts using the
| ||||||||||||||||
(defn harmonic-mean [x y] (/ 2 (+ (/ x) (/ y)))) | ||||||||||||||||
As its name implies, this function returns the harmonic mean of two numbers. If you have read the definiton carefully, you should noticed that "x and y are numbers" is not the only requirement for this function; they both are also can not be zero, and their sum can not be zero too. Otherwise, we will got an exception complaining that it's impossible to divide by zero. | ||||||||||||||||
So, let's write down our conclusions: | ||||||||||||||||
(provide-contract harmonic-mean
(c/=> [x y]
{x number?, y number?, (+ x y) (c/not zero?)}
number?)) | ||||||||||||||||
As you can see, this time slightly different syntax is used. First,
an additional argument representing the function's arguments is
given to the | ||||||||||||||||
(fact
(harmonic-mean 10 30) => 15
(harmonic-mean 10 -10) => (throws AssertionError
#"Expecting: \(\+ x y\) is: \(c/not zero\?\)"
#"Given: 0")) | ||||||||||||||||
One thing to note is that arguments' names in contract don't have to be the same as in function declaration. Correlation between them is established exclusively based on their order, but it makes things clearer when the names are the same. | ||||||||||||||||
Another topic we haven't discussed yet is functions with variable number of args. Currently, you have to explicitly define arguments when providing contract for such function: | ||||||||||||||||
(fact
(let [constrained-hash-map ((c/=> [& keys+vals]
{keys+vals (comp even? count)}
map?)
hash-map)]
(constrained-hash-map :a 1 :b 2) => {:a 1 :b 2}
(constrained-hash-map :a 1 :boo) => (throws AssertionError
#"Expecting: keys\+vals is: \(comp even\? count\)"
#"Given: \(:a 1 :boo\)"))) | ||||||||||||||||
Dependent contracts. | ||||||||||||||||
Elaborating on idea from the previous section, we can make the postcondition to depend on the function's arguments. It allows us to express in code statements like "return value must be greater than sum of the arguments" or, for example, "the function returns the argument unmodified if it satisfies certain conditions". Contracts with such postconditions are called dependent contracts. As an example, let's implement the function that computes the arithmetic mean of two numbers and take into account the fact that it's always greater than or equal to the geometric mean of the same numbers, or, in other words, that x+y divided by 2 is >= square root of x*y (this fact is known as AM-GM inequality). | ||||||||||||||||
Here is the function: | ||||||||||||||||
(defn arithmetic-mean [x y] (/ (+ x y) 2)) | ||||||||||||||||
And the contract: | ||||||||||||||||
(provide-contract arithmetic-mean
(c/=> [x y]
{x (c/and number? (c/not neg?))
y (c/and number? (c/not neg?))}
(c/<= (Math/sqrt (* x y))))) | ||||||||||||||||
Pay attention to the | ||||||||||||||||
Of course, this contract will never be violated as long as valid input is given to the function (which is ensured by preconditions) and its implementation is correct: | ||||||||||||||||
(fact
(arithmetic-mean 8 2) => 5
(Math/sqrt (* 8 2)) => 4.0
(arithmetic-mean -1 -2) => (throws AssertionError
#"Expecting: x is:"
#"\(c/and number\? \(c/not neg\?\)\)"
#"Given: -1")) | ||||||||||||||||
If you think about it, the features we've just examined allows us to generate inputs for a function based on its preconditions, and check that return values are correct as it's defined by postconditions. This is called generative-style testing; for further information on topic, check the following links: | ||||||||||||||||
| ||||||||||||||||
| ||||||||||||||||
Protocols, multimethods and beyond. | ||||||||||||||||
Warning: features described in this section are not implemented yet. | ||||||||||||||||
Features discussed in the previous section open exciting possibilities, but things become even more interesting when we realize that contracts don't limited to be applied to normal functions; nothing prevents us from associating a contract with the generic function, such as a protocol method or even a multimethod. Thus, we're able to specify an interface which all implementations must to comply with, and it's possible to make such an interface as specific as we need. To illustrate this point, let's consider a concrete example. | ||||||||||||||||
In clojure, the primary tool for creating abstractions is
protocols. For instance, one could create a protocol called
| ||||||||||||||||
(defprotocol Movement (forward [this]) (backward [this]) (ahead? [this that])) | ||||||||||||||||
(provide-contracts
(forward (c/=> [this]
{this (c/satisfies? Movement)}
(fn [result]
(ahead? result this))))
(backward (c/=> [this]
{this (c/satisfies? Movement)}
(fn [result]
(ahead? this result))))) | ||||||||||||||||
Notice that we've used | ||||||||||||||||
(defrecord TheGood [position] Movement (forward [this] (update-in this [:position] inc)) (backward [this] (update-in this [:position] dec)) (ahead? [this that] (> position (:position that)))) | ||||||||||||||||
(defrecord TheBad [position] Movement (forward [this] this) (backward [this] this) (ahead? [this that] false)) | ||||||||||||||||
And check how it works: | ||||||||||||||||
(future-facts "about the life in the one-dimensional world."
(let [good (TheGood. 1)
bad (TheBad. 1)]
(ahead? (forward good) (backward good)) => true
(forward bad) => (throws AssertionError)
(backward bad) => (throws AssertionError))) | ||||||||||||||||
As you can see, the bad implementation doesn't fit the interface we
defined for | ||||||||||||||||
But the protocols is not the only way to handle polymorphism in clojure. The other technique is to use multimethods, which allow to handle complex logic of implementation selection. | ||||||||||||||||
As you remember, we've implemented three different types of means over this tutorial: harmonic, arithmetic and geometric ones. It may be reasonable to create a generic function for computation of mean, and let user to choose dynamically which concrete type is needed in each case. The use of multimethods also keep the whole thing open, allowing to add new types of mean later. Important thing is that by definition of the mean it must be smaller than the greatest of the given numbers and greater than the smallest one. It's easier to show you the code: | ||||||||||||||||
(def ^:dynamic *type-of-mean*) | ||||||||||||||||
(defmulti mean (fn [& _] *type-of-mean*)) | ||||||||||||||||
(provide-contract mean
(c/=> [x y]
{x number?, y number?}
(fn [res]
(< (min x y) res (max x y))))) | ||||||||||||||||
Here, we define the multimethod with dispatch on dynamic var, and provide the appropriate contract. Let's add some implementations and check how they work: | ||||||||||||||||
(comment (defmethod mean :arithmetic [x y] (/ (+ x y) 2)) (defmethod mean :geometric [x y] (Math/sqrt (* x y))) (defmethod mean :broken [x y] (+ x y))) | ||||||||||||||||
(future-fact "The broken is, well, broken."
(binding [*type-of-mean* :arithmetic]
(mean 10 20) => 15)
(binding [*type-of-mean* :broken]
(mean 10 20) => (throws AssertionError
#"Expecting:"
#"\(fn \[res\] \(< \(min x y\) res \(max x y\)\)\)"
#"Given: 30"))) | ||||||||||||||||
As with protocols, the attempt to call incorrect implementation will fail, keeping the interface intact. | ||||||||||||||||
Parting words. | ||||||||||||||||
In the end of this tutorial, I would like to give some advices on how to write your own contracts: | ||||||||||||||||
| ||||||||||||||||
| ||||||||||||||||
| ||||||||||||||||
| ||||||||||||||||
| ||||||||||||||||
| ||||||||||||||||
| ||||||||||||||||
That's all what I want to tell you in this tutorial. Now go and write some contracts! | ||||||||||||||||
Copyright (C) 2012 Dmitri Naumov | ||||||||||||||||