In clojure, records, types and protocols are parts of the fundamental building blocks of the language. We have talked about defrecord here: records are wacky maps and we have unveiled deprotocol secret there: defprotocol’s secret.

Now, we are going to talk about deftype - inspired again by this great talk by Michał Marczyk: defrecord/deftype in Clojure and ClojureScript.

Importance of types

In clojure the persistent data structures are implemented in java.

In clojurescript the persistent data structures are clojure types. (This is one of those areas where clojurescript is cooler than clojure.)

Here is an excerpt from core.cljs:

(deftype PersistentVector [meta cnt shift root tail ^:mutable __hash]
(deftype PersistentQueueIter [^:mutable fseq riter]
(deftype PersistentQueueSeq [meta front rear ^:mutable __hash]
(deftype PersistentQueue [meta count front rear ^:mutable __hash]
(deftype PersistentArrayMapSeq [arr i _meta]
(deftype PersistentArrayMapIterator [arr ^:mutable i cnt]
(deftype PersistentArrayMap [meta cnt arr ^:mutable __hash]
(deftype PersistentHashMap [meta cnt root ^boolean has-nil? nil-val ^:mutable __hash]
(deftype PersistentTreeMapSeq [meta stack ^boolean ascending? cnt ^:mutable __hash]
(deftype PersistentTreeMap [comp tree cnt meta ^:mutable __hash]
(deftype PersistentHashSet [meta hash-map ^:mutable __hash]
(deftype PersistentTreeSet [meta tree-map ^:mutable __hash]

Overall, there are 74 deftype inside core.cljs

Like defrecord, deftype also fits into the definition of a record:

In computer science, a record (also called struct or compound data) is a basic data structure. A record is a collection of fields, possibly of different data types, typically in fixed number and sequence. Wikipedia

deftype is much simpler to defrecord: just constructors and getBasis; even value-based equality is not provided (see the “Types have no identity” paragraph below).

Einstein

Types creation

There are 2 ways to create a type: a constructor and a positional factory function: (A.) and (->A).

You can retrieve the basis keys of the type with getBasis that returns a vector of basis keys as symbols.

Types are plain java or javascript objects (it’s called a host type). Unlike defrecord, deftype adds almost nothing to the plain object. See the “Behind the scenes” paragraph below.

Let’s see it in action:

(deftype A [x y z])

(def a (A. 1 2 3))
(def aa (->A 1 2 3))

[a aa]
    (type a)
      (.getBasis A)
        [(.-x a) (.-y aa)]

Types are mutable

Read again the title of this paragraph.

No, you are not dreaming: clojure types are indeed mutable.

See it by yourself if you don’t believe it:

(set! (.-x a) 19)
(.-x a)

In clojure, you have to add :volatile-mutable or :unsynchronized-mutable type hint (also mutable fields mutable become private).

Like Michał said in his talk at 28m34s:

deftype is the only clojure code generation facility that gives you access to actual mutable fields of the host. And it’s fantastic. It’s like a SW development version of woodworking hand tools. It’s an apt analogy both on the power front and on the danger front.

Woodworking

Types have no identity

Like we wrote above, value-based identity is not provided by deftype. It means that that (A. 1) and (A. 1) are not equal.

(= (A. 1) (A. 1))

But it’s simple to add value and type based identity to deftype (like it is provided by defrecord).

(deftype AWithIdentity [x]
  IEquiv
    (-equiv [this other] (and
                             (= (type this) (type other))
                                                      (= (.-x this) (.-x other)))))

    (= (AWithIdentity. 1) (AWithIdentity. 1))

It’s type based so A and AWithIdentity instances are never equal:

      (= (AWithIdentity. 1) (A. 1))

Behind the scenes : deftype’s transpiled javascript code

If you are curious to see how the magic occurs in clojurescript, you will find it very interesting to observe and meditate around the transpiled javascript code of deftype:

(deftype A [x])

Clojure & Clojurescript rock!