Where should I use defrecord in clojure?
I use many maps and structs in my clojure programs. What are the benefits (apart from performance) of converting these to defrecords?
Solution 1:
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.
Solution 2:
Stuart Sierra recently wrote an interesting article on "Solving the Expression Problem with Clojure 1.2", which also contains a section on defrecord
:
http://www.ibm.com/developerworks/java/library/j-clojure-protocols/index.html#datatypes
I think the whole article is a good starting point for understanding protocols and records.
Solution 3:
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"
Solution 4:
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.