Programming with Functions #8: Monads

Maciek Gorywoda
Nerd For Tech
Published in
8 min readJul 13, 2021

In Scala, unlike for example Haskell, a monad is just a concept — there is no superclass called Monad from which all monads inherit. A monad, basically, is any wrapper class which has a static method, unit (note: this is a popular name for the method; it’s not the Unit type), which accepts an element or a collection of elements and creates a monad with them inside, and which implements flatMap, enabling us to chain operations on the monad: We can create a monad from the original element, then flat-map this element to a monad of another type, flat-map the element inside that monad, and so on. But the language does not distinguish between monad classes and non-monad classes. It’s up to developers to hold in mind the monadic rules when they write their classes.

Quite probably you can have a long and happy career as a programmer and never use a monad. But what if I could convince you that you should consider using at least some of the simpler ones? Or maybe you already used them without knowing it? There are three of them: Option, Either, and Try. They will help you avoid one thing we should all avoid: exceptions.

Don’t throw exceptions. Ever.

In one of the previous chapters, I made an example of a method dividing two doubles, div, and how a solution to it was to use one of the special constants of the class Double. It’s only out of luck that Double in Scala uses those two constants: Infinity, -Infinity, and the magical NaN. In many other situations we will not be so lucky and using certain arguments, or combinations of arguments will result in exceptions being thrown.

Throwing an exception is equivalent to throwing your hands in the air, yelling “omg, I don’t know what to do!” and running away. It doesn’t solve the problem you have. It just hands it to someone else, as if it were a hot potato. Never do that, if not for any other reason then at least for the sake of the person who would have to handle the exception instead of you. That person may be you, tomorrow.

Superficially, throwing exceptions is similar to early returns, breaks from loops, and goto statements. At some point in the past, all of them were considered valid ways of handling special situations. The function would go to perform some assertions on its arguments or intermediate results, and if an assertion failed, it would early-return with some special value. Or it would run a loop, computing complex results from elements of a collection, but if an element was something not expected, it would break the loop early. In time, a lot of criticism arose about how these constructs lead to unreadable code, difficult refactoring, and hard-to-predict outcomes, and gradually they fell out of use. I very much believe that throwing exceptions is in the same category and should fall out of use as well. There is always a better way to handle a problem than throwing an exception or returning some special value (*cough* null *cough*). And, fortunately for us, there are a few tools in Functional Programming that we can use.

Option

Option is the simplest interesting monad there is (you can always define flatMap on the Unit type — it will do nothing, but yay, it’s a monad).

You can think of an Option as a collection that can have only either zero or one element. Its unit method is simply its constructor which takes as the argument exactly one element of a given type A and returns an instance of Option[A] with that element inside. If you want to have an option with zero elements, you call method Option.empty[A]. You will still receive an instance of Option[A], even though there will be no element of the type A in it. Underneath, an option with an element is represented by the subclass Some[A] and an empty option is represented by the subclass None. If the function you’re writing usually produces a result of type A, but for some arguments it’s unable to do that, feel free to replace A in the result type declaration with Option[A] and use this functionality.

sealed trait TrollNumber
case object One extends TrollNumber
case object Two extends TrollNumber
case object Many extends TrollNumber
case object Lots extends TrollNumber

In Terry Pratchett’s Discworld series trolls are a quartz-based form of life. Their intelligence is affected by the temperature: they can be very clever when it drops close to absolute zero, but usually they are somewhat less intelligent than average, and they have a bit of trouble with big numbers, i.e. bigger than two.

[Trolls heads, by Matt Smith, from Lwiki, https://wiki.lspace.org/User:Knmatt, https://creativecommons.org/licenses/by-nc-sa/4.0/ ]

def partialTrollNumber(n: Int): TrollNumber = n match {
case 1 => One
case 2 => Two
case 3 => Many
case 4 => Lots
case _ => throw new IllegalArgumentException(“oh no!”)
}
def totalTrollNumber(n: Int): Option[TrollNumber] = n match {
case 1 => Some(One)
case 2 => Some(Two)
case 3 => Some(Many)
case 4 => Some(Lots)
case _ => None
}

Then there is the flatMap method of the class Option[A]:

def flatMap[B](f: (A) => Option[B]): Option[B]

You provide it with a function f which will take the element of the option and create a new option, of type B, based on that element. But that will work only if the option actually has an element — if Option[A] is empty, Option[B] will be empty as well.

We can also chain those flat-maps together. The result of the first called function will be passed to the second one as an argument if the result is Some, then the result of the second one will be passed to the third one if it’s Some, and so on. In the end, we will get Some(finalResult) only if all consecutive function calls produce intermediate results. If even one of them will end up with None, the final result will be None as well.

def foo(t: T): Option[X]
def bar(x: X): Option[Y]
def boo(y: Y): Option[Z]
val t: T = ...
val result: Option[Z] = foo(t).flatMap(bar).flatMap(boo)

And since an option can be treated as any other collection — after all, it’s just a list with at most one element — there’s also a wide range of methods which work like their counterparts for more regular collections, aside flatMap: map, foreach, fold, filter, and so on, even if sometimes it feels a bit weird to use them on a collection which you know may have at most one element.

And finally in Scala there is a special case: If you provide the Option constructor with null as the element, you will receive None, an empty option. You should never do it explicitly, but sometimes it may happen that you receive a null as a result of calling a third-party library written in Java. If you’re afraid that may happen, wrap the result of that call in an Option and put your own logic inside Option.foreach.

val result = javaLib.getSomethingOrNull(bar)
Option(result).foreach { res =>
// will be executed only if result is NOT null
}

In short, None, an empty option, can be used to indicate that something went wrong or that something unexpected happened, and it was not possible to calculate a valid result.

Either

Sometimes, though, we would like to know a bit more about what happened. This is the reason why there are so many types of exceptions — they are used not only to panic when something wrong happens but also to yell aloud what the tragedy was that prevented you from progressing with your computations.

Option is certainly too simple for that, but that’s why there’s Either. In its generic form, Either is like a union of two values, but on steroids. An instance of Either[A, B] can contain only one element, but that element can be either of the type A or B. Similar to how Option[A] has two subclasses: Some[A] and None, Either[A, B] also have two: Left[A] and Right[B]. So every time we have a function

def partialFoo(...): B

which can throw an exception, we can transform it into

def totalFoo(...): Either[A, B]

where B is the type of our valid result, and A is a data structure which describes the error.

But wait, there’s more. And there’s a reason why I chose B as the type of the valid result. For Either to be a monad, there has to be a preference: the unit method must take only one argument, the element itself, and it has to choose by itself which subclass to use. Also, the flatMap method needs to choose what “path” does it take — do we flat-map to the left or the right? Scala made an arbitrary decision in favour of Right[B], thus forever oppressing left-handed people, like me ( 😢 ). Thanks to that, we can now chain “eithers”: If we return a result from a function as an Either, we can then flat-map it to another function. That function will then receive the result of the first function, but only if the result is of the subclass Right[B]. The second function can then produce another result of the type Either[C, D] which then can be chained to yet another function, etc.

But if any of those chained functions produces an error, the processing stops at that link in the chain, by-passes all consecutive function calls and returns the error from the whole chain. That’s the beauty of using monadic Either for error handling: it has the advantage of handling exceptions in only one place, without all the mess which exceptions create.

case class Error(msg: String)
def makeX(t: T): Either[Error, X]
def makeY(x: X): Either[Error, Y]
def makeZ(y: Y): Either[Error, Z]
val t = T()
val result: Either[Error, Z] =
makeX(t).flatMap(makeY).flatMap(makeZ)
// the result is either the valid outcome of
// the whole flat-map chain, or the first of the encountered errors

In theory, the left part of Either could be used for anything. It’s only how the monadic rules were implemented that make it particularly suited for handling errors. And we use for it both the data itself — for example, as above, the error can consist of a text message describing the error — but also we can take advantage of the type system and create our hierarchy of errors. Error can be a trait, and then we can have classes of IO errors, network errors, invalid state errors, and so on. This is how the default Java hierarchy of exceptions works, and if we feel lazy, we can use that hierarchy instead of creating our own. For example:

def petBlackCat(cat: Cat): Either[Throwable, Purr] = 
if (cat.colour != Black)
Left(new IllegalArgumentException(“this is not a black cat”))
else
Right(pet(cat)) // the method `pet` produces a purr

In the next video we will continue this topic with the Try monad, the for/yield syntax, and a bit of theory about what a monad exactly is. See you then.

Previously in the series: Expressions over statements

Links

Nerd For Tech
Nerd For Tech

Published in Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/.

Maciek Gorywoda
Maciek Gorywoda

Written by Maciek Gorywoda

Scala. Rust. Bicycles. Trying to mix kickboxing with aikido. Trying to be a better person too. Similar results in both cases. 🇪🇺 🇵🇱

Responses (1)

Write a response