Contracts programming for Clojure.
(require '[trammel.provide :as provide])
(defn sqr [n] (* n n))
(sqr 10)
;=> 100
(sqr 0)
;=> 0
(provide/contracts
[sqr "given a number not equal to zero, sqr ensures that it returns a positive number"
[x] [number? (not= 0 x) => number? pos?]])
(sqr 10)
;=> 100
(sqr 0)
; Pre-condition failure: given a number not equal to zero, sqr
; ensures that it returns a positive number
; Assert failed: (not= 0 x)
(use '[trammel.core :only (defconstrainedrecord)])
(defconstrainedrecord Foo [a b]
"Foo record fields are expected to hold only numbers."
[(every? number? [a b])]
Object
(toString [this] (str "record Foo has " a " and " b)))
;; default ctor with default values
(->Foo 1 2)
;=> #:user.Foo{:a 1, :b 2}
;; use like any other map/record
(assoc (->Foo 1 2) :a 88 :c "foo")
;=> #:user.Foo{:a 88, :b 2, :c "foo"}
;; invariants on records checked at runtime
(assoc (->Foo 1 2) :a "foo")
; Pre-condition failure: Foo record fields are expected to hold only numbers.
; Assert failed: (every? number? [a b])
(use '[trammel.core :only (defconstrainedtype)])
(defconstrainedtype Foo [a b]
"Foo type fields are expected to hold only numbers."
[(every? number? [a b])])
(->Foo 1 2)
#<Foo user.Foo@73683>
;; invariants on types checked at constructions time
(->Foo 1 :b)
; Assert failed: (every? number? [a b])
(def a (constrained-atom 0
"only numbers allowed"
[number?]))
@a
;=> 0
(swap! a inc)
;=> 1
(swap! a str)
; Pre-condition failure: only numbers allowed
(compare-and-set! a 0 "a")
; Pre-condition failure: only numbers allowed
The same will work on all reference types, including:
send
and send-off
, assertion errors handled as normal agent errorsbinding
Modify your Leiningen dependencies to include Trammel:
:dependencies [[trammel "0.7.0"] ...]
Add the following to your pom.xml
file:
<dependency>
<groupId>trammel</groupId>
<artifactId>trammel</artifactId>
<version>0.7.0</version>
</dependency>
Trammel is in its infancy but I think that I have a nice springboard for experimentation and expansion, including:
defconstraint
-- with ability to relax requires and tighten ensuresanything
constraint cheap (elimination)provide-contracts
more amenable to REPL useIf you have any ideas or interesting references then I would be happy to discuss at me -the-at-sign- fogus -the-single-period- me.
Add the following to your .emacs file for better Trammel formatting:
(eval-after-load 'clojure-mode
'(define-clojure-indent
(contract 'defun)
(defconstrainedfn 'defun)
(defcontract 'defun)
(provide 'defun)))
Type the following into a REPL session to see how Trammel might be used.
(defconstrainedtype Bar
[a b]
[(every? pos? [a b])])
(Bar? (->Bar 1 2))
(defn sqr [n] (* n n))
(provide-contracts
[sqr "the constraining of sqr"
[n] [number? (not= 0 n) => pos? number?]])
(sqr 0)
(positive-nums -1)
(type (->Bar))
(.a (->Bar 42 77))
(.b (->Bar 42 77))
(.a (->Bar -42 77))
(.b (->Bar 42 -77))
(defconstrainedfn sqrt
[x] [(>= x 0) => (>= % 0)]
(Math/sqrt x))
(defn- bigger-than-zero? [n] (>= n 0))
(defconstrainedfn sqrt
[x] [bigger-than-zero? => bigger-than-zero?]
(Math/sqrt x))
(sqrt 10)
(sqrt -19)
(defconstrainedfn sqrt
[x] [bigger-than-zero? => bigger-than-zero? (<= (Math/abs (- x (* % %))) 0.01)]
(Math/sqrt x))
(* (sqrt 30) (sqrt 30))
(def ag (constrained-agent 0
"only numbers allowed"
[number?]))
(send ag str)
@ag
(agent-error ag)
(def r (constrained-ref 0
"only numbers allowed"
[number?]))
(dosync (alter r inc))
(dosync (alter r str))
(def a (constrained-atom 0
"only numbers allowed"
[number?]))
@a
(swap! a inc)
(swap! a str)
(compare-and-set! a 0 "a")
(defconstrainedvar ^:dynamic foo 0
"only numbers allowed in Var foo"
[number?])
(binding [foo :a] [foo])
Can you improve this documentation? These fine people already did:
fogus, Fogus & Michael HarrisonEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close