Using the Either Monad in Clojure

Recently at work we've started up two interesting study groups: Haskell and Category Theory. The category theory group we've been going through Bartosz Milewski's series of videos 'Category theory for computer scientists', and Haskell we've been building a small application.

During our category theory session a few weeks ago we were talking about the Kleisli Category and it reminded me of some code in one of our applications.

The code is focused around some transformations (some pure, some not) and the error handling for these transformations. We have a collection of objects we want to transform individually and then save them to the database. If we can't transform an object we want to be notified using Airbrake and logging, but everything that can be saved should be. Wrapping the entire thing in a try-catch block felt lazy and I thought we could do better.

I'm not going to show what actually happens in any of these transformations, all you need to know is that they take a value, and either return a value or throw an exception. None of them will gracefully handle being passed a nil value. The great thing about the refactoring is that we don't need to change any of the transformations and none of them need to do any kind of error handling.

The reason this reminded me of the Kleisli Category is the re-definition of comp. It needs to get redefined so that the composition will short circuit when a function returns a nil value. With the knowledge that this is similar to a pattern we can refactor into something even more recognisable.

Our new refactoring we separate our handling of the errors from doing something about the error. We've also changed our redefinition of comp, here it knows about the structure of values that functions are expected to return (a tuple - using Clojure's vector). If you're familiar with Haskell or Scala the pattern should be quite obvious. What we've implemented is similar to the Either type (a clj implementation of Either). Let's go ahead and pull in the Cats library and refactor this again.

Now with the Cats library we don't need to redefine comp, we instead use foldm which is like reduce but it understands the monadic context that our functions return.

This isn't a massive change to the code, we have the same number of functions we started with and roughly the same number of lines of code. However, I found it interesting that the code I had written some time ago was very similar to an existing pattern and it was a fun exercise to refactor my code into use a monad library. I also want to look at refactoring this again to use the exception monad.

Comments