Road to Haskeller #15 - Monad

Last Edited: 6/28/2024

The blog post introduces one of the most important typeclasses, Monad, in Haskell.

Haskell & Monad

Recap

In the previous two articles, we covered Functor, which can execute a function on a value inside a type constructor with fmap, and Applicative, which can execute a function in a functor on a value inside another functor with pure, <$>, and <*>. If you are not confident about functors and applicative functors, check out the articles, Road to Haskeller #13 and #14.

Monad

Now, we are finally ready to talk about Monad. Monad is a natural evolution of Applicative, which allows a function that takes an input of type a and outputs an applicative with a value in type b to be performed on an applicative with a value of type a. Let's look at the definition of Monad to clarify what I mean by that.

class Applicative m => Monad (m :: * -> *) where
    (>>=) :: m a -> (a -> m b) -> m b
    (>>) :: m a -> m b -> m b
    return :: a -> m a
    fail :: String -> m a
    {-# MINIMAL (>>=) #-}

We can see from MINIMAL that we only need to define (>>=) or bind for an Applicative to become a Monad. We are passing an Applicative with a value of type a and a function that takes an input of type a and outputs an Applicative with a value of type b to arrive at an Applicative with a value of type b. This was not at all possible with Functors nor Applicative because the former only accepts a function (a -> b), and the latter only accepts (a -> b) in a functor, not (a -> m b). To understand Monad better, let's look at some examples.

Maybe

Maybe is an instance of Monad defined as follows.

instance Monad Maybe where
    return x = Just x
    Nothing >>= f = Nothing
    Just x >>= f  = f x
    fail _ = Nothing

The return function is the same as pure, as it wraps a value in Just. The bind function or >>= uses pattern matching to output Nothing when Nothing is passed and the result of applying a function to a value inside Just otherwise. This is useful because we now can use a function that outputs a monad, meaning we can set up our own condition for Nothing.

When we were using Applicative, we could not get Nothing unless one of the inputs was Nothing. This is because the functions had to have an output of type inside of Maybe and not Maybe itself. Now that we can have a function that takes a value of type a and returns Maybe a, we can do something like the following:

safediv :: (Eq a, Fractional a) => a -> a -> Maybe a
safediv x y
  | y == 0 = Nothing
  | otherwise = Just (x / y)
 
Just 1 >>= (\x -> safediv x 10) --- (Just 0.1)
Just 1 >>= (\x -> safediv x 0) --- (Nothing)

Now, we can apply safediv of type (a -> a -> m a) to a value inside of Maybe. This allows us to have more flexibility with when we return Nothing in our operations aside from the case where Nothing is passed (like safe guarding agaist y == 0). This can help us make a more robust functions.

Lists

A list is also a valid instance of Monad defined like the following:

instance Monad [] where
    return x = [x]
    xs >>= f = concat (map f xs)
    fail _ = []

The return function is the same as the pure function again, which puts a value in a list. The bind function maps a function over elements in a list and concatenates the results to arrive at a list. The following demonstrates how the bind function works for a list:

[3,4,5] >>= \x -> [x,-x]
--- [3,-3,4,-4,5,-5]

The anonymous function \x -> [x, -x] is applied to each element in a list and the results, [3, -3], [4, -4], and [5, -5] are concatenated to make a final list. Aside from Maybe and lists, there are many other type constructors that belong to the Monad typeclass, including IO.

Do Syntax

When using the bind function to use functions with multiple parameters, such as safediv, the following is one way of how we can achieve it:

res = Just 1 >>= (\x -> Just 2 >>= (\y -> safediv x y))

However, ut is not as simple to write and read as we want it to be. Hence, Haskell made better syntax for achieving the same thing:

res = do
    x <- Just 1 --- x = 1
    y <- Just 2 --- y = 2
    return $ safediv x y --- Just 0.5

Wait, have we not seen something like this somewhere else? Yes, this is the notation we saw in Road to Haskeller #12 - Hello, World (IO actions)! <- is actually the same thing as >>= (same name "bind"!), and do is for eliminating parentheses. Some of you might have noticed this, but we have seen <- notation somewhere else too. It actually appeared in the article, Road to Haskeller #3 - Lists & Tuples, when we learned about list comprehensions!

--- List Comprehension
res = [2*x | x <- [1,2,3]] --- res = [2,4,6]
 
--- Do Notation
res = do
    x <- [1,2,3]
    return $ 2 * x --- res = [2,4,6]
 
--- <<= Notation
res = [1,2,3] >>= (\x -> return $ 2 * x) --- res = [2,4,6]

Monad Laws

There are laws that a type needs to abide by for it to be a valid Monad. The rules are the following:

  • Left Identity: This states that putting a value into a monad and applying a function using bind should result in the same output as passing the value directly to the function. (return x >>= f == f x)

  • Right Identity: This states that passing a value inside of a monad to return using bind should just result in the same monad with the same value. (m >>= return == m)

  • Associativity: This states that the result should be the same regardless of how nested a chain of functions made with bind is. (m >>= f >>= g == (m >>= f) >>= g)

Exercises

This is an exercise section where you can test your understanding of the material introduced in the article. I highly recommend solving these questions by yourself after reading the main part of the article. You can click on each question to see its answer.

Resources