The blog post introduces one of the most important typeclasses, Monad, in Haskell.
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 _ = NothingThe 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.5Wait, 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
returnusing 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
- Philipp, Hagenlocher. 2020. Haskell for Imperative Programmers #17 - Monads. YouTube.
- NA. A Fistful of Monads. Learn You a Haskell for Great Good.