Haskell: Monad.
The Monad
or at least my attempt at explaining it
-- The use of this will be explained later
> import Control.Monad ((<=<))
A monad is a haskell Class - this means it comes with specific methods. Let’s
have a look at what ghci
has to say about it.
λ> :i Monad
class Applicative m => Monad (m :: * -> *) where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
{-# MINIMAL (>>=) #-}
-- Defined in ‘GHC.Base’
instance Monad (Either e) -- Defined in ‘Data.Either’
instance Monad [] -- Defined in ‘GHC.Base’
instance Monad Maybe -- Defined in ‘GHC.Base’
instance Monad IO -- Defined in ‘GHC.Base’
instance Monad ((->) r) -- Defined in ‘GHC.Base’
instance Monoid a => Monad ((,) a) -- Defined in ‘GHC.Base’
Ok, so we know it is a subclass of Applicative, we can see it is implemented for all of these data structures and it comes with some funky looking operators, namely:
-
(>>=)
aka. bind -
(>>)
which I like to call “and then” -
return
- which is the same aspure
fromApplicative
I could go on and on about the theoretical foundations of it, but I’d rather focus on a trivial example.
The identity Monad
Let’s define our own Monad from scratch to try to understand how it works - for this I propose the simplest of all lawful monads - the Identity monad. The reason why I call it a monad is because it behaves like one - i.e. the instance of Monad is lawful (don’t get bogged down, yet in terminology - but keep in mind).
> data Identity a = Identity a
As you can see, our Identity data type takes any value and places it
inside. It’s so basic that we can guarantee that if we have an Identity a
we
can always get its content - let’s write a function that does just that:
> getContent :: Identity a -> a
> getContent (Identity x) = x
Mentally we can think of Identity as a Box.
So what can we say about it? Well firstly - we can be sure it has a lawful
Functor instance. Given any Box a
and a function a - > b
we can obtain a
Box b
:
> instance Functor Identity where
> fmap f (Identity a) = Identity $ f a
Nice. We can also see that given any boxed function Identity (a -> b)
and a
boxed value Identity a
we can get a boxed Identity b
value. Furthermore,
given any a
we can always box it into Identity a
. This is an easy win and we
get our Applicative
instance.
> instance Applicative Identity where
> (Identity f) <*> (Identity a) = Identity $ f a
> pure a = Identity a
Ok, so we’ve done all the footwork up until the Monad instance. Let’s have a
closer look at the bind operator (>>=)
.
Monad m => (>>=) :: m a -> (a -> m b) -> m b
If we specialise this operator to what we’re trying to achieve it looks more like this:
(>>=) :: Identity a -> (a -> Identity b) -> Identity b
We have the following, a boxed value a
, a function from a
to a boxed b
,
and at the end the result is a boxed b
. Mentally, we can think of the second
function as something that unboxes the first value, and then returns our new box
- let’s see this in our implementation.
> instance Monad Identity where
> (>>=) (Identity value) boxingFunction = boxingFunction value
Can you see what we’ve done? We’ve unboxed the value, and passed it to our
boxingFunction. Ok, maybe it isn’t very clear - but let’s take a second and have
a chat about it. Our data type Identity is useless - in many ways it could
disappear from all of our implementations because it doesn’t give us much. We
could really push it and say that fundamentally Identity a
and a
are one and
the same thing - only one is wrapped in this mental box and the other isn’t. We
could say that our bind is
(>>=) :: a -> (a -> something b) -> something b
and if we squint we could almost say that in this instance, Identity doesn’t
really give us much more information about anything. We could even say that it’s
(&) :: a -> (a -> b) -> b
from Data.Function
.
In any case - why does this matter - and how is it relevant? Well here’s why it
matters - let’s take Maybe as another example. Say we have the function half
which returns the half of an even number or Nothing
if it is an odd number.
> maybeHalf :: Int -> Maybe Int
> maybeHalf x
> | even x = Just $ x `div` 2
> | otherwise = Nothing
λ> maybeHalf 1
Nothing
λ> maybeHalf 2
Just 1
Squinting again we see something which we’ve already seen. maybeHalf
’s type
looks vaguely familiar - indeed its type is the same as the second argument of
(>>=)
namely ` a -> m b or in our case
a -> Maybe b, or even more specific
Int -> Maybe Int`. That’s great because now we can use the operator:
> maybeQuarter :: Int -> Maybe Int
> maybeQuarter x = maybeHalf x >>= maybeHalf
Ok, so what happened now? Think of (>>=)
as taking the result of maybeHalf x
and passing it to the function maybeHalf
again. That’s neat because, when we
see it action, it actually makes a lot of sense.
λ> maybeQuarter 2
Nothing
λ> maybeQuarter 4
Just 1
The do notation
Another cool thing Monads unlock is haskell’s do notation. Let’s refactor the
above maybeQuarter
to leverage this.
> maybeQuarterDo x = do
> half <- maybeHalf x
> quarter <- maybeHalf half
> return quarter
The two are one and the same function - but in different notations. Look at the
do notation one, and then look at the bind
notation one. Some intuition might
emerge.
Fundamentally, a Monad is a type class that allows us to take an instance of itself containing a value, and a function from that value to another instance of itself containing possibly a different value. This allows us to easily compose monads. One last example
The fish (<=<)
The fish, is the epitome of what I just said because in the context of a monad
it is the moral equivalent of our composition operator (.)
.
λ> :t (>=>) :: Monad m => (b -> m c) -> (a -> m b) -> a -> m c
λ> :t (.) :: (b -> c) -> (a -> b) -> a -> c
You can see it, right? So how could we refactor our maybeQuarter
now?
Whatabout:
> maybeQuarterFish :: Int -> Maybe Int
> maybeQuarterFish = maybeHalf <=< maybeHalf
End
That was it. I’m sure it all makes sense now - maybe.