Cogs and Levers A blog full of technical stuff

Monads

Introduction

Following on from my previous posts about functors, applicative functors and applying applicative it would only be natural for me to post a follow up on the topic of Monads. Monads take shape very similarly to applicative functors so for this post to make sense, I suggest you read the previous articles so that you have a chance to follow along.

What is a Monad?

At a code level, a Monad is a type that is an instance of the Monad type class. At a concept level a Monad allows you to provide a function that is context-unaware to operate over values that are wrapped in a context. Immediately, you’d think that this is weaker functionality that what was defined for applicative functors - and you’d be right in thinking so, but Monads do provide a very natural way to write code against our types. Here is the type definition for a Monad.

class Monad m where
  return :: a -> m a

  (>>=) :: m a -> (a -> m b) -> m b

  (>>) :: m a -> m b -> m b
  x >> y = x >>= \_ -> y

  fail :: String -> m a
  fail msg = error msg

Moving through all of the defined functions, you first see return. return is the same as pure which we defined when making an applicative functor. return’s role in this type is to lift a value into a context. The next function you see is >>= which is pronounced “bind”. This is the major difference right here. >>= is what takes a value wrapped in a context m a and takes a function with a parameter of a returning a wrapped b as m b, with the function returning a b in a context m b. The remaining two operations are rarely delved into as their default implementations suffice majority of scenarios.

Laws

There are some laws that need to be abided by when implementing Monads. Left Identity suggests that

x >>= f is the same thing as f x

Right Identity suggests that

m >>= return is the same as m

Associativity suggests that

(m >>= f) >>= g is the same as m >>= (\x -> f x >>= g)

Such that it shouldn’t matter how these calls are nested together.

Custom context

Applying this knowledge to our rather useless example “CustomContext” discussed here, we can make this type a monad with the following definition.

instance Monad CustomContext where
  return = CustomContext         
  CustomContext x >>= f = f x    

Ok, so - as prophesied return is doing the same thing as pure did for us - it’s just lifting a plain value into our context. >>= or “bind” on the other hand is taking the value out of its context and applying a function to it. We can check out our bind implementation in action with the following simple examples.

λ> CustomContext 3 >>= \x -> return (x + 1)
CustomContext 4
λ> CustomContext "Hello" >>= \x -> return (x ++ " World!")
CustomContext "Hello World!"

Our value is having a function applied to it. Values are getting de-contexted, worked-on than re-contexted. Pretty straight forward. To show you how >>= can work for us, I’ve created a function that will accumulate even numbers as they are received into it. If an odd number is encountered, the accumulated total get wiped back to zero until conditions are met to have another two consecutive calls with even numbers. Here’s how the function looks.

accumEven :: Int -> Int -> CustomContext Int 
accumEven y x                                
  | even x    = CustomContext (y + x)       
  | otherwise = CustomContext 0             

Using guards, we filter out odd numbers sending the result back to zero otherwise we continue to accumulate. Note how the inputs are just plain integers but the output is an integer wrapped in one of our CustomContext types. With the use of >>= the following calls are possible.

λ> accumEven 0 2
CustomContext 2
λ> accumEven 0 2 >>= accumEven 4
CustomContext 6
λ> accumEven 0 2 >>= accumEven 4 >>= accumEven 7
CustomContext 13

First of all, we’re freely using wrapped and un-wrapped contexts thanks to that bind operator. More interestingly (and demonstrative to our purposes here), the last example appears to be in error, but it’s really not. Think about it this way.

accumEven 0 2 = 2
accumEven 4 2 = 6
accumEven 6 7 = 13

So you can see here that the next sub-sequent call to accumEven would finish with a 0 (zero) value as the 13 would be carried onto the next call and would be tested for evenness. Even if this isn’t the best example, it still demonstrates how bind is sending information between the calls.

do Notation

Another nicety when working with Monads is do notation. do notation allows you to clean up a lot of the boilerplate that builds up around the functionality you write when working with monadic values. Here’s a very simple function that builds a CustomContext from two other CustomContexts by adding them.

CustomContext 6 >>= (\x -> CustomContext 7 >>= (\y -> CustomContext(x + y)))

You can see how things start to get a bit gnarly once you chain more calls together. Using do notation, this gets cleaned up pretty well. The above can be re-written like so.

makeContext = do        
  x <- CustomContext 6 
  y <- CustomContext 7 
  CustomContext (x + y)

That’s so much easier to read! There’s do notation at work for you, anyway. Well, that’s all there is for Monads right now. As soon as I try something useful with them, I’ll post a follow-up article of Monads in Application.