Skip to content
master
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Trammel

Contracts programming for Clojure.

Clojars Project

Example

Function Contracts

    (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)

Record Invariants

    (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])

Type Invariants

    (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])

Reference Invariants

    (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:

  • Refs - Invariants checked in a transaction
  • Agents - Invariants checked on send and send-off, assertion errors handled as normal agent errors
  • Vars - Invariants checked on binding

Getting

Leiningen

Modify your Leiningen dependencies to include Trammel:

    :dependencies [[trammel "0.7.0"] ...]

Maven

Add the following to your pom.xml file:

    <dependency>
      <groupId>trammel</groupId>
      <artifactId>trammel</artifactId>
      <version>0.7.0</version>
    </dependency>

Notes

Trammel is in its infancy but I think that I have a nice springboard for experimentation and expansion, including:

  • Contracts for higher-order functions
  • Better error messages
  • Distinct pre and post exceptions
  • Study the heck out of everything Bertrand Meyer and Walter Bright ever wrote (in progress)
  • defconstraint -- with ability to relax requires and tighten ensures
  • Study the heck out of Racket Scheme (in progress)
  • Modify macros to also allow regular Clojure constraint maps
  • Make the anything constraint cheap (elimination)
  • Allow other stand-alones: true/false, numbers, characters, regexes
  • Make provide-contracts more amenable to REPL use
  • Generate a Foo? function (in progress)
  • Marrying test.generative with Trammel

If you have any ideas or interesting references then I would be happy to discuss at me -the-at-sign- fogus -the-single-period- me.

References

Emacs

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)))

Example REPL Session

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])

About

Contracts programming with Clojure

Resources

License

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •