Monads for OO Programmers

I’ve been writing Scala for 5 years (At time of writing this post). Let me explain in my own layman terms without category theory what a Monad is.

A Monad is an object instance which contains another object instance and adds some context to it.

The word “context” here means to add other information to something in order to give it meaning. If you saw the word Drisenburg in a story book it’s meaning wouldn’t necessary be apparent out of context, but in the context of the book, you would come to understand as you read the whole book it is a name for a character or a location or treasure of something.

Context is new information that changes the meaning of something. A joke might be objectively funny but it might not be a funny thing if said at someone’s funeral.

We may know a Royal wedding is on TV at 5pm in UK Time but we need to know our own timezone or location, for that time to be relevant to us. In this example the time is the thing or object and the timezone is the context that makes the time relevant for us.

As a general phrase many people say “to put things into context…” and in the Object Oriented design world it makes exactly the same sense to model this context as something that wraps something else.

case class UKWeddingEvent(
   dayOfYear :Int,
   hour :Int,
   minute: Int
)

case class LocalTimeUKWeddingEvent(
   ourOffsetFromUKTimeInHours :Int,
   weddingEvent :UKWeddingEvent
)

Here I have a UKWeddingEvent and a LocalTime which I’m calling the Context in this example.

We could arguably model our solution by using a Tuple to attach our timezone to our wedding event

val ukWeddingEvent = UKWeddingEvent(364, 12,00)    // thing
val localOffsetFromUKHours = 4                     // context

val myLocalisedWeddingEvent = (localOffsetFromUKHours, ukWeddingEvent)

Or we could just have some sort of Context object to pair them

case class Context[A, B](mainThing :A, extraData :B)

The last two proposed solutions aren’t ideal as they allow tinkering with the mainThing outside of its context, without the full facts about what’s changing.

If the UK Wedding is pushed back two weeks, you don’t want to tamper with the UKWedding directly outside of the context of the localising information as you will cause a bug. The clocks go forward an hour in the summer in the UK one week before the Americans’ put their clocks forward so this would introduce a bug into the system. This is why the best approach was the original design, one that controls the object to restrict it’s access, which was very close to our first example:

case class UKWeddingEvent(
   dayOfYear :Int,
   hour :Int,
   minute: Int
)

class LocalTimeUKWeddingEvent(weddingEvent :UKWeddingEvent, offset :Int) {
   private val weddingEvent :UKWeddingEvent        // thing
   val offsetHoursFromUK :Int = 6                  // context
}

We still need to be able to change the wedding event in a controlled manor and in a way where the change can be captured. One way to do this is to allow people to provide an update function which changes a UKWeddingEvent into a new UKWeddingEvent, mapping it from an old value into a new value and allowing us to handle context changes:

def map(function :UKWeddingEvent => UKWeddingEvent) = {
   val newEvent = function(weddingEvent)
   
   val wasBST = weddingEvent.dayOfYear > 65 && weddingEvent.dayOfYear < 135

   val nowIsBst = newEvent.dayOfYear > 65 && newEvent.dayOfYear < 135

   val newOffset = (wasBst, nowIsBst) match {
        case (false, false) => offset
        case (true, true) => offset
        case (false, true) => offset - 1
        case (true, false) => offset + 1
   }
   
   new LocalTimeUKWeddingEvent(newEvent, newOffset)
}

So the function above allows users of my Context class to manipulate the inner object is a way that allows the context to retain control safely. Callers may use it like this:

def delayWeddingByTwoWeeks() = {
    localWeddingEvent.map(event => event.copy(dayOfYear = event.dayOfYear + 14))
}

This has safely pushed back the wedding whilst letting the context stay accurate and relevant. Whilst these next two examples may seem a little contrived for our code, it’s time to step back a little a look at the big picture.

I’ve been calling a Monad a generic context object so there’s little surprise we are going to make this UKWeddingEvent use the generic terms that monads use.

I’m going to eventually make this UKWeddingEvent in a Monad. Two other common things we want to do with our context object apart from map over it, is change the context by simply taking an update to itself it:

def flatMap(function :LocalWeddingEvent => LocalWeddingEvent) = {
   val replacementForWholeContext = function(this)
   this.offset = replacementForWholeContext
}

Flattening a monad is a way of merging contexts. In our example, it would be applying two timezones to the event. Convert a UK Time to French time, and then on to German (providing a new context and thing).

So lets look at our LocalUKWedding object and talk about those few operations

class Context {
   def pure(in)   // put a thing into some context
   def map(f)     // apply a function to a thing
   def flatMap    // replace a thing + context
   def flatten    // reduce to a single context
}

someOtherFunction   // get something out of the monad and realise it's value.

A Tour of real Monads.

Future

Functions return values and the Future Monad abstracts away the details of how functions are turned into values. Understanding the context of running code on this thread or that thread and have a slightly irritating need to refer to ExecutionContexts.

So Futures provide values and allow you to manipulate values that may not have even been generated yet by exposing it’s map function to say “apply this function to this value when it becomes available”.

// code example required.

I’m going to talk about Futures again when I get near the bottom. For now understand it is a Monad that provides an execution context.

Options

The context is whether something can be missing. map() here takes a function but runs it if the thing exists, thus your code can be ignorant of null pointers and missing variables and run safely regardless because it provides the context of whether or not those commands would fail.

What’s nice about abstracting away whether something exists or not is that you can write really terse code with practically no if statements.

You can think of Option’s implementation as a list of 0 or 1 items. If you can write a LinkedList collection class in Java, you can easily write your own Monad for Option. map takes a function and only runs it if the item exists.

Either

An either is two values bundled together. Either the left is empty and the right is populated or the right is empty and the left is populated. A little surprising thing about Either is that it’s “right biased”. map() takes a function that applies to the thing on the right. map does nothing to the left. Scala developers put good values in the right and errors in the left and map works like a “if this is successful, continue with this…”.

Getting things out

There’s no secrets or cheats here but it seems to be a point of frustration or confusing for people. You can put things into Monads, operate on them in context (whilst ignoring the context) and then pull them out. Every monad allows you at runtime to realise it’s value in a concrete way. Even future. You simply “Await.result”. Every program using a Future either has one await.result in it or it prematurely ends. No Exceptions. In Play framework the framework does it. Akka and http4s too. You should avoid it completely and chain your operations using map and flatMap. This will give you an application that has flexible concurrency and little chance of deadlocks. But there is no magic, eventually the whole app is flattened into one future per event handler on which await.result is invoked. The function isn’t in Monad category theory language but is called Await.result.

Programs using Option either eventually call .get or .getOrElse or they use if statements to control the flow but somewhere they are realised.

Next steps

Once you’re familiar with the four operations of a Monad. Put it in (pure), apply a change to the value safely (map), replace one (flatmap) or flatten nested Monads (flatmap) then you’re ready to look at some of the other guides with more confidence and experiment!

Scala has a very nice set of keywords that allow you to write a series of maps followed by a single flat map in a pretty style. It’s a syntax variation on a for loop called a for-yield loop.

You are probably going to want to explore that at some point but it’s beyond the subject of this blog post and frankly is just pretty boilerplate that shouldn’t deter you from using Monads directly. Hopefully this guide compliments your understanding of Futures and Monads from other articles and you can play with them having more confidence.