This episode is for subscribers only. Sign Up or Log in to watch it.

24. defrecord and defprotocol

Published 30 January 17

With records, Clojure has introduced a way to create struct-like objects, while avoiding some of the problems encountered in object orientation. Records don’t hide their data behind custom interfaces, but instead partake in the abstractions that Clojure’s own data types are built on, so that code can treat treat a record like a regular Clojure data type.

Protocols are Clojure’s take on abstract interfaces. They make it possible to extend preexisting types to new methods, and extend preexisting methods to new types, thus avoiding the expression problem.

Show notes

Many programming languages have some kind of “struct” type, a way of creating values with specific named fields. Clojure calls these records, for example a Book record could have an author, title, and year it was published.

defrecord takes the name of the record type, and then a vector forms with the field names.

(defrecord Book [author title year])

defrecord is an extension of deftype, so everything you know about types also applies to records. You can construct them the same way, with an interop style constructor, or with the arrow constructor function, and you can access fields with dot forms. If you’re not familiar yet with deftype, I recommend checking out episode 23, which covers everything you need to know.

(defrecord Book [author title year])

(Book. "Ursula K. Le Guin" "The Dispossessed" 1974)
(->Book "Ursula K. Le Guin" "The Dispossessed" 1974)

(.title (->Book "a" "b" 1999))

That said, even though records can do everything that types can, you don’t normally use them the same way. That’s because records behave like proper Clojure data types. With deftype in order to have your custom type partake in Clojure’s abstractions you have to manually implement all the necessary interfaces. Records already come with implementations for a dozen of those interfaces, which means you can treat them like any other Clojure value. In particular they behave a lot like maps.

This starts with the constructor. Besides the arrow constructor that takes positional arguments, records also provide a constructor which takes a map, with keywords for keys that correspond with the record’s field names.