Yet another Monad, Applicative tutorial with Cats

Krisztian Lachata
Making Gumtree
Published in
6 min readMar 22, 2017

I know that there are hundreds of tutorials in this topic but I still feel the need to write another one as simple and pragmatic as possible since I meet developers on a daily basis who cannot get those concepts. Even in the highly paid contractor range which really makes me upset

Let’s suppose we have to model a simple car store. The store has a number of listed cars for selling. A car in the store is described with some arbitrary properties. VRM (Vehicle Registration Mark) and Price will do the job. On top of that, the store provides a couple of convenient functionality for buyers to be able to compare cars. For the sake of simplicity let’s assume that the potential buyer is interested in two cars and would like to know how much is the price difference between them.

  • Buyer provides two VRMs
  • If both of the cars are available then compute the price difference
  • If one of them is not available then return with a relevant error
  • For demonstration purpose the price is expressed as Long

Java-like naive implementation

This is obviously java code dressed in scala syntax. Very verbose, deep-nested if-else branches and throws exception. The method signature doesn’t say anything about the possible catastrophe. If one of the cars doesn’t exist in the store an exception will be thrown! Another typical solution is to return with null instead of throwing exception. Both of the solutions forces the caller to open the method to obtain more knowledge about the implementation. Follow “the Principle of Least Astonishment”.

A bit less naive implementation

We have managed to eliminate the exception and potential null check issues but the if-else branches are still making our code verbose and fragile. Fortunately this type of solution is not common, but still can be seen time to time in scala programs.

Scala-like naive implementation

This solution can be seen more often. It is a bit more functional and scala like but this is just syntactically hiding the if-else statements. It is somehow shorter but the readability still can be improved.

The benefit of the last 3 solutions is that they make the decision tree pretty obvious:

  • If the first car is not available then return with error
  • If the first car is available then fetch the second car
  • If the second car is not available then return with error
  • If the first and the second car is available then calculate the final result

Now we see the pattern!!

We need the calculated result at the end of the happy path but we would like to terminate immediately when some intermediate calculation fails.

Monad/FlatMap is the plumber of the happy path!

Principles of Reactive Programming

Erik Meijer

It is very useful for short-circuiting a computation!!

Scala implementation with a bit of Cats boost

Armed with this knowledge we will implement a solution with a Monad. Sounds good. Which one to use? There are tons of different monad types inside and outside of the default scala library. Just to mention some:

  • Option[T] — A monad for maybe effect
  • Try[T] — A monad for exception effect
  • Future[T] — A monad for async effect
  • Either[E, T] — A monad for error effect. Yes! It is right bias since scala 2.12. No need for Scalaz’s Disjunction (\/) or Cats’s Xor (which has already been removed anyway)
  • Read[T, A] — A monad for expression dependencies (Cats)
  • State[S, A] — A monad for state effect (Cats)
  • Task[A] — A monad for lazy & asynchronous computations, useful for controlling side-effects (Monix)
  • There are many other more exotic monads like Free, Eff but it goes beyond this tutorial.

For this task the most convenient choice is Either since it communicates well the purpose of the method, handles error cases, easy to use and has a really good support in Cats. The result of the calculation will be either an error type at the left side or a value at the right side.

Just to sum up what we have now:

  • If the first car is not available then return with error
  • If the first car is available then fetch the second car
  • If the second car is not available then return with error
  • If the first and the second car is available then calculate the final result
  • No exception and null handling
  • Clear method signature
  • Short and concise solution
  • The first thing comes into my mind about Monad is for-comprehension since it expresses the plumbing and short-circuiting in a neat and readable way
  • I am using Cats to boost the default Either to be able to create from Option

Monad/FlatMap is the plumber of the happy path!

If you master this simple concept the whole buzz around monads will become much more understandable and will see this pattern everywhere.

Collecting all the error messages

Ok, ok but what if I have to collect all the error messages happens during my computation? I would like to know if none of the cars are available!

In this case Monad is not the right choice. Monad is for sequencing effects and short-circuiting a computation when something went wrong. You need something else which is called Applicative functor. Unlike Monads, Applicative functor encodes working with multiple independent effects.

For collecting all the error messages you can use Validated type and Applicative Builder from Cats.

It will produce a valid Long or an invalid non empty error list result. The second car will be tried to be fetched independently from the result of the first attempt. Monad does short-circuit computation when something goes wrong meanwhile Applicative functor continues computation and collects all the results.

This solution can be widely used in any type of data validations.

The buyer is interested in the total price of multiple cars

Let’s assume the store provides possibility to buy more cars and luckily our buyer is willing to pay for them. But first he needs the total price for a number of given cars.

  • Buyer provides an arbitrary number of VRMs
  • If all of the cars are available then computing the total price
  • If one of them is not available then returning with a relevant error

The concept behind this solution is the following:

  • Every VRM is converted to Either[Error, Long] where the Left value will be Error if the car is not available or a price in the Right when the car is available
  • As a result we have a List[Either[Error, Long]] but we need an Either[Error, Long] and this is where traverse comes into play (which is a kind of combined map and sequence). It produces an Either[Error, List[Long]]. The Either’s value will be Left Error when one of the provided VRMs not available in the list or Right List[Long] if all the cars were available.
  • All we have left is to sum the Right List[Long] value to get the total price

This trick works as long as traverse function produces an Applicative type. And we know that Either is a Monad and also Applicative on top of that. Because Either has 2 type parameters the default traverse is not working and some extra Cats magic is needed to be able to traverse on it that is why travereseU is used instead.

The buyer is interested in the total price of multiple cars and also all the potential errors :)

You know what comes next. Yes. Our precious buyer needs all the error messages. By now you should know the pattern.

We have to use Validated instead of Either what is an Applicative as well.

The concept behind this solution is very similar to the previous one:

  • Every VRM is converted to Validated[NonEmptyList[Error], Long] where the Left value will be NonEmptyList[Error] (NonEmptyList is a special list type where it always have at least one item) if the car is not available or the Right price when the car is available
  • As a result we have a List[Validated[NonEmptyList[Error], Long]] but we need a Validated[NonEmptyList[Error], Long] (ValidatedNel[Error, Long] is just an alias in for Validated[NonEmptyList[Error], Long]) and this is where traverse comes into play. It produces a ValidatedNel[Error, List[Long]]. The ValidatedNel’s value will be Left NonEmptyList[Error] with all the Errors where the provided VRMs not available in the list or Right List[Long] if all the cars were available. NonEmptyList is a Monoid as well which is needed for the Error aggregation.
  • All we have left is to sum the Right List[Long] value to get the total price

Thank you for the proof reading for Rene Weber, Kristof Jozsa

--

--