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
2
3
4
5
6
7
8
9
10
11
12
(defn valid-number-of-args
[args n]
(if (= n (count args))
(right args)
(left (str "invalid number of arguments, expected " n))))

(defmethod do-command "F"
[_ state & args]
(m/mlet [[x y colour] (valid-number-of-args args 3)
pixel (valid-pixel state x y)
_ (valid-colour colour)]
(m/return (flood-fill pixel colour state))))

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
2
3
4
5
6
7
8
9
10
11
12
(defn valid-number-of-args
[args n]
(if (= n (count args))
args
(throw (Exception. (str "invalid number of arguments, expected " n)))))

(defmethod do-command "F"
[_ state & args]
(let [[x y colour] (valid-number-of-args args 3)
pixel (valid-pixel state x y)
_ (valid-colour colour)]
(flood-fill pixel colour state)))

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.