Trammel

a contracts-programming library for Clojure

Contracts Programming

the relationship between a class and its clients as a formal agreement, expressing each party's rights and obligations

read more

defconstrainedfn

Defining a function with an associated contract is similar to the way that one defines a regular Clojure function:

(defconstrainedfn sqr
  "Squares a number"
  [n] [(number? n) (not= 0 n) ;; requires/domain
       =>
       (pos? %) (number? %)] ;; ensures/range
  (* n n))

Simply put this definition states the expectations on the domain and the range of a function, per function arity:

(defn a-function 
  [args] [DOMAIN => RANGE]
  ...)

Or to put it another way, it describes what arguments a function requires in order to ensure accuracy:

(defn a-function 
  [args] [REQUIRES => ENSURES]
  ...)

Disecting the definition of sqr you can see how Trammel operates:

  • Define a named function sqr
  • sqr takes a single argument n
  • sqr requires that n be a number not equal to zero
  • sqr ensures that it will return a positive number
  • sqr performs the action (* n n)

You can see this in action below:

(sqr 5)
;=> 25

(sqr -5)
;=> 25

(sqr 0)
; java.lang.AssertionError: Assert failed:
(not (zero? n))

As you might expect given the contract, the calls to sqr with 5 and -5 succeed but the call with 0 fails. The example of sqr is the simplest model for defining a function adhering to a contract. However, you can also define a function allowing different argument arities, as seen below:

(defconstrainedfn doubler
  "Defines a function that doubles its input."
  ([n] [(number? n) => (= (* 2 n) %)]
     (* 2 n))

  ([x y] [(every? number? [x y])
          =>
          (number? %)
          (= (* 2 (+ x y)) %)]
     (* 2 (+ x y))))

The form of the contract for doubler is more involved, but its meaning is still straight-forward:

  • Define a function named doubler that takes either one or two arguments
  • The first form of doubler takes a single argument n
    • doubler requires that n be a number
    • doubler ensures that it returns the double of n – which it does by executing (* 2 n)
  • The second form of doubler takes two arguments x and y
    • doubler requires that x and y both be numbers
    • doubler ensures that it returns the double of the sum of x and y – which it does by executing (* 2 (+ x y))

You can also use isolated functions in the body of the constraints and the arguments specified in the enclosing arity specification will be passed into it implicitely, as seen below:

(defconstrainedfn sqr
  "Squares a number"
  [n] [number? (not= 0 n) => pos? number?]
  (* n n))

And that’s all there is to it.

return to documentation

clojars → source code → tickets → wiki →