You've got a monad in me


Recently, design patterns are terribly popular, especially on job interviews, so let’s take a look at its Wikipedia entry:

In software engineering, a design pattern is a general reusable solution to a commonly occurring problem within a given context in software design.

There are many good books and articles on design patterns but you cannot reap the benefits just by reading about them.

A prerequisite for truly assimilating patterns is to have a prior background: having experience the problem in its context and, somehow solved or sidestepped it. Ideally, when you read about a pattern, you should recognize it as an acquaintance you have met before. However, you can get more or less amazed about how close your solution was to the canonical one.

From this point on, the newly acquired pattern will yield dividends. Not in the form of letting you solve a problem you couldn’t previously solve since you need that prior experience to really embrace the pattern. True dividends include faster problem identification, easier communication1 and being able to implement a less idiosyncratic solution.

Monads, monads everywhere

Functors and monads are very common functional programming design patterns and they are waiting for you to carve them out of your code pretty much everywhere. Once you get familiar with FP and read about these design patterns2 you develop the same intuition about this that about when to use a singleton.

I want to illustrate this with a recent example from my day job.

Caching information

First, imagine you have some piece of data Foo that is obtained by a fallible polling from a remote server and you should track if your cached data is fresh or stale. It is reasonable to use a Boolean for this purpose at this stage.

case class Foo(
    accountId: String,
    balances: FiatAmounts,
    isFresh: Boolean
)

Later, you add another piece of information Bar with similar characteristics.

case class Bar(
    accountId: String,
    activePeriod: Interval,
    transferLimits: FiatAmounts,
    isFresh: Boolean
)

After this change, you realize that the design is taking a wrong course: you have duplication and primitive booleans spreading to more and more places3. So you replace the plain Boolean with the richer type CacheStatus.

sealed trait CacheStatus {
  def isFresh: Boolean
}

object CacheStatus {
  case object Fresh extends CacheStatus {
    override def isFresh = true
  }
  case object Stale extends CacheStatus {
    override def isFresh = false
  }
}

To remove the duplication we could introduce a generic type Cached[T] that will allow you to annotate anything with freshness information:

case class Cached[A](cached: A, status: CacheStatus)

Spiderman senses a monad

Now your spider-sense goes off yelling like mad at you, “monad! monad!”. In practical terms a monad can be seen as any container or context holding information (i.e. with one generic parameter) that supports and implementation of map and flatMap4 that won’t surprise you.

If you add those methods to Cached[T] then you can transform cached values without losing the information about freshness.

scala> val cachedGreet = Cached.fresh("hello")
scala> cachedGreet.map(_.size)
Cached(5, Fresh)

And you can also combine cached values using the expressive comprehension syntax having guaranteed that the result will be fresh only if all the inputs are fresh.

scala> val cachedName = Cached.stale("John")
scala> for {
     |   greet <- cachedGreet
     |   name <- cachedName
     | } yield s"$greet $name!"
res1 = Cached(hello world!, Stale)

You can take a look at the completed Cached[T] implementation here.

No silver bullet

Design patterns, functional or OO, are not silver bullets that you can harness just by invoking their names. You need to invest time gaining context and studying them before getting your spider-sense tuned for helping you when to use them.

  1. Software development is a social endeavor after all. 

  2. If you approach this by reading a monad tutorial as the first step you are doomed. You need to get some field experience on FP and only then, chase the monad. 

  3. This looks like the primitive obsession smell 

  4. There are alternative and more formal definitions of what a monad is and the monad laws in the Typeclassopedia