• Stars
    star
    242
  • Rank 167,048 (Top 4 %)
  • Language
    Clojure
  • License
    Eclipse Public Li...
  • Created over 14 years ago
  • Updated over 10 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Contracts programming with Clojure

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

More Repositories

1

lemonad

a functional programming library for javascript. an experiment in elegant JS.
JavaScript
658
star
2

thunks

posts and code related to personal studies
374
star
3

himera

JavaScript
279
star
4

ulithp

A micro LISP implementation in 24 lines of Ruby.
Ruby
270
star
5

papers-i-love

Computer science and computer-adjacent papers (and sometimes books) that have influenced me deeply.
261
star
6

lithp

McCarthy's Lisp in Python with macros. A celebration of 50+ years of symbolic processing.
Python
257
star
7

baysick

An embedded Insane-specific Language for Scala implementing the BASIC programming language
Scala
246
star
8

bacwn

clojure + datalog = <3
Clojure
137
star
9

minderbinder

converting one thing into another thing via Clojure.
Clojure
88
star
10

codd

a relational algebra library for JavaScript
JavaScript
60
star
11

skiing

a clojure combinator zoo
Clojure
44
star
12

tori-lisp

an ersatz lisp for tiny birds. a code riff.
JavaScript
42
star
13

evalive

various eval functions and macros for use with clojure. a code riff.
Clojure
33
star
14

unfix

Infix and Postfix library for Clojure that was cut from the book.
Clojure
31
star
15

lang-genealogy

Forth
9
star
16

reinen-vernunft

Code conversations in Clojure regarding the application of pure reasoning algorithms.
Clojure
8
star
17

tathata

An implementation of Clojure pods.
Clojure
7
star
18

thneed

An eclectic set of Clojure utilities that I've found useful enough to keep around.
Clojure
5
star
19

gamut-of-games

a repository of rules for games in text formats
4
star
20

fogos

Assembly
3
star
21

russ-forth

Russforth is a teeny-tiny Forth-esque implementation in Ruby.
Ruby
3
star
22

tafl

a clojure library to manipulate and query table-like data structures.
Clojure
3
star
23

spec-experiments

Clojure
1
star
24

battlestation

a description of my home battle station
1
star
25

abs-oddity

Clojure
1
star