Monads as Design Patterns

Posted on July 8, 2018
Go to comments.

Functional abstractions like Monads are often presented in the contexts of typeclasses, with attendent libraries and syntax to learn.

I think typeclasses are fantastic. But I also think that this creates an additional hurdle. So I’d like to focus on abstractions as design patterns. We want to solve an issue; we have some goal in mind which we don’t know how to reach yet, but the pattern gives us some relatively easy-to-assemble but not immediately straightforward methods to reach that goal.

With that in mind, today I’d like to cover Monads, with a glance at Applicatives as well (later, I’ll cover the similarities and differences between them). In the next post, I’ll present Comonads as a similar design pattern.

So to be clear:

Goal: To be able to compose multiple items with the same added functionality Pattern: Implement flatMap, map, and unit methods.

(You may notice that I’m taking a Scala approach here instead of the previous Haskell-based posts. First, if you’re learning Haskell, you have probably already been convinced of the use of abstractions such as Monads. Second, I’m working as a Scala dev now, so this is my life at the moment, for good or for ill.)

The Reader Monad

To start, let’s take the Reader monad. Reader[R,A] is dependency injection - give a value of type R, and use it to calculate some A. The following example doesn’t use any typeclass magic or anything, or actually any libraries at all; just the basic few functions required by a Monad so that we can compose steps.

And with that, let’s create some sort of config which can be passed in as a dependency:

Now that we have the setup, let’s create some config-dependent values:

We have two different ways to combine these before having to pass in any configuration - we can stack our Reader legos together to get more Readers. The first way uses the fact that we implemented flatMap and map to use Scala’s for syntax:

And the second way uses the sequence function we implemented to be able to take an entire sequence of config-dependent values (perhaps determined dynamically at runtime) and wrap them into a single config-dependent value:

Now let’s run all three versions with configuration added:

The State Monad

The Reader monad allows us to read in dependencies, but not to alter them. It is essentially a wrapper around a function R => A. If we want to update the value passed in, we’ll have to make sure that we output the updated version. This would give us a function such as: S => (A, S). We can reuse most of the above code:

It’s no fun having State to modify if we don’t modify it, so let’s add some functions to do so:

We’ll reuse our Readers and have them modify the state while they read it:

Running it once gives us the same results as before, except it also returns an updated version of our Config:

But we can also try running it multiple times in succession:


Return to post.