Traits support to clojure's deftype and defrecord, as a library
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


A library to get traits support to clojure's deftype, defrecord, extend and extend-type.

The purpose of this library is to provide a mechanism of code-reuse for those occasions when using a map and extend is not fast enough, or, in the case of java interfaces instead of clojure protcols, extend would simply be impossible to use.


With the 0.3.0 release, :defaults has been replaced by :traits.

Since release 0.3.3 traits must contain the declaration of the interfaces/protocols that the methods implemented belong to, if they don't, deftrait will throw an exception.

Because of their nature, both extend and extend-type won't work with traits implementing interfaces.


In Leiningen:

:dependencies [[bronsa/neurotic "0.3.5"]]


First of all, require neurotic.traits:

user=> (ns my-ns
         (:refer-clojure :exclude [deftype extend])
         (:require [neurotic.traits :refer [deftype deftrait extend]]))

Next, let's define a protocol with two functions:

my-ns=> (defprotocol AProtocol (f [_]) (g [_] [_ _]))

We can create a trait using deftrait:

my-ns=> (deftrait ATrait [] AProtocol (g [_] 1))

deftrait requires the following args:

  • name: the name of the trait
  • required args: a vector containing the required args an implementing deftype/defrecord must provide that must match the mutable declaration (will be explained later)
  • body: the body of the trait, containing interface/protocol methods implementation, and the interfaces/protocols that are going to be implemented

Now that we have created the trait, let's see how to use it in a deftype

my-ns=> (deftype AType [] :traits [ATrait])

We can see that it worked:

my-ns=> (g (AType.))

Let's create another trait, implementing the complete protocol

my-ns=> (deftrait ATrait2 [] AProtocol (g [_] 0) (g [_ i] i) (f [_] 0))

Both ATrait and ATrait2 implements the single-arity version of g; what happens when we implements both the traits in a new type?

my-ns=> (deftype AType2 [] :traits [ATrait2 ATrait])
my-ns=> (let [a (AType2.)] [(g a) (g a :foo) (f a)])
[1 :foo 0]

We can see that when multiple traits implementing the same function and arity, the implementation of the last trait specified wins.

Additionaly, the implementation provided in the body of a deftype wins over all the traits:

my-ns=> (deftype AType3 [] :traits [ATrait2 ATrait] (f [_] 2))
my-ns=> (f (AType3.))

You can notice that we didn't need to specify in the body of deftype that we were implementing the protocol AProtocol since it's already specified in at least one of the implemented traits.

Let's now see how the required-args mechanism works:

Lets' define two traits

my-ns=> (deftrait ATrait3 [foo])
my-ns=> (deftrait ATrait4 [^:volatile-mutable foo])

Let's see what happens when implementing those traits

my-ns=> (deftype AType4 [foo] :traits [ATrait3])
my-ns=> (deftype AType5 [foo] :traits [ATrait4])
Exception Mutable declaration mismatching for one or more args  my-ns/eval960 (NO_SOURCE_FILE:1)
my-ns=> (deftype AType6 [^:volatile-mutable foo] :traits [ATrait4])
my-ns=> (deftype AType7 [] :traits [ATrait4])
Exception deftype declaration is missing the following args: foo, required by one or more implementing traits  my-ns/eval967 (NO_SOURCE_FILE:1)

As you can see, deftype is throwing exceptions if a required-arg is missing, or if it's mutable declaration is mismatching between one or more traits, or between traits and deftype declaration.

Everything shown to work with deftype works with defrecord too.

my-ns=> (defprotocol AProtocol2 (h [_]))
my-ns=> (deftrait ATrait5 [] AProtocol2 (h [_] 1))
my-ns=> (deftype AType8 [])
my-ns=> (extend AType8 :traits [ATrait2 ATrait5])
my-ns=> (g (AType8.))
my-ns=> (h (AType8.))


Copyright © 2012-2015 Bronsa

Distributed under the Eclipse Public License, the same as Clojure.