This blog post was originally posted on my blogpost blog at this URL, and was later migrated to this place. There may be some comments at the original URL.
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.
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:
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:
- Metadata: Most Clojure data structures implement protocol
clojure.lang.IObjthat provides an ability to decorate references with metadata, without actually affecting the data. We can store “type tags” for our data in this metadata!
- 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:
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.
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:
- There could be messages (tag dispatched polymorphic functions) with arity greater than one. The dispatch function should handle that.
- We could define a variation of
defmessagewhich takes a function value. This would be useful for 3.
- Combinators for common cases, such as “sequence of $type-tag”.
- 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.
- 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: