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