In my previous blog post, I talked about embedded document pattern, and how a combination of combinators and some metaprogramming can abstract away its recurring bits. When I wrote that post, I wondered how I might deal with this problem in Clojure, a language that lacks OO, in traditional sense. I fiddled with it a bit and ended up with what I find to be a reasonably elegant solution. This blog post talks about that.

Open shell

In Clojure, we like to keep our data structures “naked”. We don’t wrap them in classes unless there is a good reason to do so. This has a huge advantage that all the functions and utilities available for maps, sets, sequences still work with your data.

Our initial approach to the problem might look like:

(def data
  {:id "product1234"
   :name "Nex-5R"
   :href ""
   :links [{:id "link1"
            :url ""}
           {:id "link2"
            :url ""}]})
(defn product-master-id 
  (:name product))
(defn link-master-id 
  (:id link))
; Collect product master ID and master IDs for all its links
{:product-master-id (product-master-id data)
 :links-master-ids (map link-master-id (:links data))}

Pretty simple. Just data and functions.

However there is a disadvantage with this approach: The caller always has to know the type of the input data. (link, product etc.) In a system involving complex data, comprising of dozens of models, this would become very tedious. This is not ideal.

Wouldn’t it be nice if we could have our data respond to function calls polymorphically, without having to wrap it in some types or having to otherwise pollute the data? The good news is that there are a couple of Clojure features that make this possible:

  1. Metadata: Most Clojure data structures implement protocol clojure.lang.IObj that provides an ability to decorate references with metadata, without actually affecting the data. We can store “type tags” for our data in this metadata!
  2. Multimethods: These let you dispatch on arbitrary function. In our case, we could use a dispatching function that looks up the type tag we embedded in the metadata.

With that our design might look like:

(defn with-type-tag
  [data type-tag]
  (with-meta data {:type-tag type-tag}))

(defn type-tag
  (:type-tag (meta data)))

(defn tag-as 
  (fn [data] (with-type-tag data tag)))

(defmulti master-id type-tag)

(defmethod master-id ::Product
  (:name this))

(defmethod master-id ::Link
  (:id this))

(defmulti links type-tag)

(defmethod links ::Product
  (->> this :links (map (tag-as ::Link)))

; Collect product master ID and master IDs for all its links

{:product-master-id (master-id data)
 :links-master-ids (map master-id (links data))}

Great, we got a custom tagging and dispatching system working with a tiny bit of code!

Since we are always dispatching on a type-tag, we could spin up a small macro that avoids some of this duplication for us.

(defmacro defmessage
  [name' tag & forms]
     (defmulti ~name' type-tag)
     (defmethod ~name' ~tag

; Here's how relevant part of previous snippet will look like after we start 
; using this macro

(defmessage master-id ::Product
  (:name this))

(defmessage master-id ::Link
  (:id this))

(defmessage links ::Product
  (->> this :links (map (tag-as ::Link)))

Cool, with just a few more lines, we also have a custom syntax for our new system!

What you see here is not a novel idea, but something very routinely used in Lisps. (Some refer to it as “custom type system”, though I personally am not a fan of that term, as the term “type” means something entirely different to me.)

We have done a pretty fine job here, but there is a lot of room for improvement:

  1. There could be messages (tag dispatched polymorphic functions) with arity greater than one. The dispatch function should handle that.
  2. We could define a variation of defmessage which takes a function value. This would be useful for 3.
  3. Combinators for common cases, such as “sequence of $type-tag”.
  4. Some sugar that lets one define messages for a model together in one place. Note that this will syntactially be somewhat like OO classes, but won’t limit the extensibility, because what we are essentially writing are multimethods.
  5. You could extend this further to include things like validations. A macro for model could generate a “tagger” that will run these validations before tagging given data.

All of the above left as exercise for curious/adventurous readers. ;-)

If you found this post interesting, here are some more links/resources that might strike your fancy:

  1. Chris Granger on the design of LightTable
  2. “We really don’t know how to compute!” by Gerald Sussman
  3. The Art of MetaObject Protocol