Either monad or exceptions
The
Either
monad from cats is pretty cool.
Recently I was asked to complete a small test for a job interview in the form of a Clojure program. The idea of the program was to take commands from stdin and use them to mess around with a ‘canvas’, basically a super-primitive graphics program. It occured to me that since part of this would involve parsing and validating commands and displaying any errors, it might be a good opportunity to use the Either monad and see the current state of monad libraries in Clojure.
I have used algo.monads in the past but looking at the project on github I could see that it hadn’t been updated in more than 11 months. I thought that perhaps there has been some work done in this area since then and I discovered cats. It has some nice documentation and a nice philosophy so I decided to give it a try.
The program flow is:
- read-line from the console
- split the input by the space character
- dispatch the arguments and the current state of the program to a multi-method, based on the first argument
- return the new state or a string to display on the console
- loop round to read-line again
This flow allows me to use pure functions for the multi-method as in the following command which flood-fills an area of the canvas:
1 | (defn valid-number-of-args |
mlet
is analogous to do notation in Haskell. valid-number-of-args
, valid-pixel
and valid-colour
are functions that return either a Right
containing the value or a Left
containing an error.
It’s probably clear that I could have used functions that throw an Exception with the error message, the above would then look like:
1 | (defn valid-number-of-args |
You could argue that the second version, apart from being slightly more terse, is more idiomatic however I don’t believe this is true, Exceptions in Java were never intended to be used for unexceptional flow, it’s just that they have become commonly used in this way, precisely because they are basically a strange form of the Either monad, why not just use the real thing when dealing with non-exceptional flow? It also looks better if you can avoid instantiating java classes such as Exception
in my opinion.
In this particular case the flow is not ‘exceptional’ at all since some of the commands such as ‘S’ which displays the current state on the console, should return a string even though there is no ‘error’. Having a function that always throws an Exception
and yet is successful seems a bit dodgy to me. With Either
you are not saying that Left
are errors but rather that they are strings that should be displayed to the user.
I have also been using promises in the form of manifold a lot recently and the semantics of this are almost exactly the same as Either
and mlet
above, a manifold deferred
is essentially an async Either
. Add to this that manifold can be used with cats and it adds up to a more consistent way of doing things. Everything looks the same, it’s just a matter of whether you want something to be async or not.
Finally, because an Exception
will keep going up the call-chain until it is caught, it’s possible to introduce bugs where the Execption
disappears into the ether, this can be a pain to discover sometimes. Using the Either
semantics, Clojure’s type system will usually throw a runtime exception and problems will be slightly easier to spot.
My conclusion is that I like using Either
in Clojure and I like cats :) I think I will use both in the future. It’s also nice to note that cats and some related libraries are moving Clojure in directions other than what the clojure.core team is doing. As clojure.core gets better and more innovative, the libraries and tools do as well. It makes me glad to be using Clojure and hopeful for it’s future.