I use many maps and structs in my clojure programs. What are the benefits (apart from performance) of converting these to defrecords?
5 Answers
I consider structs to be effectively deprecated so I don't use them at all.
When I have a fixed set of well-known keys used in many map instances, I usually create a record. The big benefits are:
- Performance
- Generated class has a type that I can switch on in multimethods or other situations
- With additional macro machinery around defrecord, I can get field validation, default values, and whatever other stuff I want
- Records can implement arbitrary interfaces or protocols (maps can't)
- Records act as maps for most purposes
- keys and vals return results in stable (per-creation) order
Some downsides of records:
- Because records are Java class instances (not Clojure maps), there is no structural sharing so the same record structure will likely use more memory than the equivalent map structure that has been changed. There is also more object creation/destruction as you "change" a record although the JVM is designed specifically to eat this kind of short-lived garbage without breaking a sweat.
- If you are changing records during development you probably need to restart your REPL more frequently to pick up those changes. This is typically only an issue during narrow bits of development.
- Many existing libraries have not been updated to support records (postwalk, zip, matchure, etc etc). We've added this support as needed.

- 43,109
- 15
- 131
- 205

- 69,183
- 25
- 122
- 167
-
The constructor form you describe in your third argument is what Common Lisp refers to as a "BOA Constructor"--*by order of argument*, which is used in concert types defined with `defstruct`. See http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_b.htm#boa_lambda_list. – seh Jan 01 '11 at 23:20
-
Hi Alex - I think you will find that records actually do use structural sharing under the hood - they implement a full persistent data structure, so they keep all the advantages of regular maps. – mikera Jan 02 '11 at 07:21
-
Mikera - a record generates a Java class with final fields. Associng into one must generate a new Java object. It can reuse field instances since they're immutable but it's not as efficient as a normal Clojure map. – Alex Miller Jan 02 '11 at 13:00
-
What is defrecord2 you are referring to? I cannot find it nor in core nor in contrib. – Petr Gladkikh Feb 02 '11 at 12:16
-
So basically a record is what I use in Clojure when I would use a class in OO? (Consider field validation etc. very important, compared to an unsafe map). – User Mar 29 '14 at 13:57
-
Yes, records are designed to represent "information entities" which map reasonably well to domain objects in OO. – Alex Miller Mar 31 '14 at 02:04
-
3I would argue that maps are also easier to serialize/deserialize using, for instance, edn. I remember having some issues reading back records previously serialized, because the code doing the deserialization couldn't find the compiled code for the record type that I had defined, though I eventually sorted it out somehow. Had I used just maps, I would have avoided that problem completely. – Rulle Jul 26 '16 at 22:01
-
The claim that records consume more memory than maps seems dubious to me. In Clojure's current implementation, small maps (less than 8 keys or so) are implemented with an array that gets copied for each change, so no structural sharing there either - plus you have to store the values *and* the keys. – Valentin Waeselynck Dec 23 '16 at 15:39
-
@AlexMiller the conflicting points about performance and no structural sharing is left confusing here. Why would it harm performance unless the record has more than a handful of elements to it? – matanster May 20 '17 at 11:36
Stuart Sierra recently wrote an interesting article on "Solving the Expression Problem with Clojure 1.2", which also contains a section on defrecord
:
I think the whole article is a good starting point for understanding protocols and records.

- 3,397
- 21
- 22

- 66,324
- 14
- 138
- 158
One other major benefit is the record has a type (its class) you can dispatch off of.
An example that uses this feature but is not representative of all possible uses is the following:
(defprotocol communicate
(verbalize [this]))
(defrecord Cat [hunger-level]
communicate
(verbalize [this]
(apply str (interpose " " (repeat hunger-level "meow")))))
(defrecord Dog [mood]
communicate
(verbalize [this]
(case mood
:happy "woof"
"arf")))
(verbalize (->Cat 3))
; => "meow meow meow"
(verbalize (->Dog :happy))
; => "woof"

- 4,183
- 2
- 23
- 42
-
2Not really, since you can dispatch on any function, not just type. You can use an ordinary map and, say, add another entry with a key named "type". Then you can use a dispatch function that checks the value of "type" entry. – Goran Jovic Jan 02 '11 at 00:29
-
6@Goran, that applies to multimethods, using protocols, you can only dispatch on type. Regardless, my point was using defrecords adds a type implicitly while a defstruct or map does not automatically add a type. – bmillare Jan 02 '11 at 04:09
-
-
Use maps in most cases and records only when you require polymorphism. With maps alone you can still use multimethods; however, you need records if you want protocols. Given this, wait until you need protocols before resorting to records. Until then, avoid them in favor of more data-centric and simpler code.

- 6,572
- 3
- 42
- 74
In addition to what has been previously noted, besides being generally at par or superior in terms of performance, and in exposing the same programming interface as a map, records enforce mild structure: key names and the number of keys are enforced at the time of definition. This might be useful in avoiding silly errors where the same structure is expected from many values (or just artificially rigid otherwise).
Whatever the original motivations, this property too sets it apart from maps.

- 15,072
- 19
- 88
- 167