In clojure, records, types and protocols are parts of the fundamental building blocks of the language.
We have unveiled defprotocol’s secret in a previous post. Now it’s time to explore defrecord.
This article has been - inspired by this great talk by Michał Marczyk: defrecord/deftype in Clojure and ClojureScript.
Michał knows this topic very deeply as he contributed to the implementation of types, records and
protocols in clojurescript in 2012.
He’s also the #4 contributor on clojurescript.

We were very honoured when Michał encouraged us to write a blog post on the topic using KLIPSE.
Introduction
What is 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
In clojure, defrecord, deftype and defprotocol were introduced in version 1.2
In clojurescript the persistent data structures (maps, vectors …) are based on types, records and protocols.
Records: creation and identity
There are 3 equivalent ways to create a record: a constructor and two factory functions: (A.), (->A) and (map->A).
(defrecord A [x y])
(def a (A. 1 2))
(def aa (map->A {:x 1 :y 2}))
(def aaa (->A 1 2))
[a aa aaa]
You can retrieve the basis keys of the record with getBasis that returns a vector of basis keys as symbols.
(.getBasis A)
Records of the same type and same values are equal
(= a aa aaa)
And have the same hash code.
(map hash [a aa aaa])
Records of different types are never equal (even if they have the same values).
(defrecord B [x y])
(def b (B. 1 2))
(= a b)
In clojurescript, the hash function doesn’t take the record type into account.
(map hash [a b])
Maps
At first glance, records behave like maps: keyword access, get, count, keys and iteration work as expected.
[(:x a) (get a :z "n/a")]
(keys a)
(count a)
(map (fn [[k v]] [k (inc v)]) a)
But records are not real maps. Michał called them Wacky maps.
Wacky maps
Here are the differences between records and maps:
- Unlike a regular map, a record is not callable as a function.
- When you
assoca record you get another record. - When you
dissocbasis keys in a record you get a map instead of a record. - In
clojureboxing happens on records when you use type hints; but not inclojurescript. (Boxing can be avoided with type hints, if you use field access syntax and you’re in a primitive-friendly context e.g. an arithmetic expression.)
assoc works as expected:
(assoc a :z "zzz")
But dissoc is surprising:
(dissoc a :x)
Behind the scenes : defrecord’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 defrecord:
(defrecord A [x y])
If you want to discover stuff you didn’t know about deftype, go to The power and danger of deftype…
Clojure & Clojurescript rock!