Monad
28 min read
Learning Outcomes
- Understand that MonadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
extends FunctorA type class in Haskell that represents types that can be mapped over. Instances of Functor must define the fmap function, which applies a function to every element in a structure.
and ApplicativeA type class in Haskell that extends Functor, allowing functions that are within a context to be applied to values that are also within a context. Applicative defines the functions pure and (<*>).
to provide a bindthe defining function which all monads must implement.
(>>=)
operation which allows us to sequence effectful operations such that their effects are flattened or joined into a single effect. - Understand the operation of the monadic bindthe defining function which all monads must implement.
and join functions in the
Maybe
,IO
, List and Function instances of MonadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context. . - Be able to refactor monadic bindsthe defining function which all monads must implement.
using
do
notation. - Loop with Monadic effectsOperations that produce side effects and are managed within a monadic context, ensuring that the effects are sequenced and controlled. .
Introduction
As with FunctorA type class in Haskell that represents types that can be mapped over. Instances of Functor must define the fmap function, which applies a function to every element in a structure.
and ApplicativeA type class in Haskell that extends Functor, allowing functions that are within a context to be applied to values that are also within a context. Applicative defines the functions pure and (<*>).
the name MonadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
comes from Category Theory. Although the names sound mathematical they are abstractions of relatively simple concepts. A FunctorA type class in Haskell that represents types that can be mapped over. Instances of Functor must define the fmap function, which applies a function to every element in a structure.
allowed unary functions to be applied (mapped/fmap
ed) over a context. ApplicativeA type class in Haskell that extends Functor, allowing functions that are within a context to be applied to values that are also within a context. Applicative defines the functions pure and (<*>).
allowed us to apply a function in a context to values in a context. So too, MonadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
has a characteristic function called “bindthe defining function which all monads must implement.
”, which allows us to perform another type of function application over values in a context.
The special thing about bindthe defining function which all monads must implement.
is that it allows us to chain functions which have an effect without creating additional layers of nesting inside effect contexts. People often try to describe MonadsA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
in metaphors, which are not always helpful. The essence of MonadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
really is bindthe defining function which all monads must implement.
and there is no getting around looking at its type signature and seeing what it does in different instances, which we will get to shortly. However, one analogy that resonated for me was the idea of bindthe defining function which all monads must implement.
as a “programmable semicolon”. That is, imagine a language like JavaScript which uses semicolons (;
) as a statement separator:
// some javascript you can try in a browser console:
const x = prompt("Name?"); console.log("Hello "+x)
As we will see shortly, the Haskell bindthe defining function which all monads must implement.
operator >>=
can also be used to sequence expressions with an IO effect:
getLine >>= \x -> putStrLn("hello "++x)
However, it not only separates the two expressions, it is safely handling the IO
type within which all code with IO side-effects in Haskell must operate. But as well as allowing us to chain effectful operations, bindthe defining function which all monads must implement.
is defined to do different and useful things for different MonadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
instances, as we shall see.
The Monad Typeclass
As always, we can interrogate GHCi to get a basic synopsis of the MonadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context. typeclass:
> :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 [safe] Monad m => Monad (ReaderT r m)
-- Defined in `transformers-0.5.2.0:Control.Monad.Trans.Reader'
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'
Things to notice:
Monad
is a subclass ofApplicative
(and therefore also aFunctor
)return
=pure
, fromApplicative
. Thereturn
function exists for historical reasons and you can safely use onlypure
(PureScript has onlypure
).- the operator
(>>=)
(pronounced “bindthe defining function which all monads must implement. ”) is the minimal definition (the one function you must create—in addition to the functions also required forFunctor
andApplicative
—to make a newMonad
instance). >>
is a special case of bindthe defining function which all monads must implement. (described below)- lots of built-in types are already monadsA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
There also exists a flipped version of bindthe defining function which all monads must implement. :
(=<<) = flip (>>=)
The type of the flipped bindthe defining function which all monads must implement.
(=<<)
has a nice correspondence to the other operators we have already seen for function application in various contexts:
(=<<) :: Monad m => (a -> m b) -> m a -> m b
(<*>) :: Applicative f => f (a -> b) -> f a -> f b
(<$>) :: Functor f => (a -> b) -> f a -> f b
($) :: (a -> b) -> a -> b
So the bindthe defining function which all monads must implement.
function (>>=)
(and equally its flipped version (=<<)
) gives us another way to map functions over contexts—but why do we need another way?
As an example we’ll consider computation using the Maybe
type, which we said is useful for partial functionsFunctions that do not have a mapping for every input, potentially failing for some inputs.
, that is functions which are not sensibly defined over all of their inputs. A more complex example of such a function than we have seen before is the quadratic formula which, for quadratic functions of the form:
determines two roots:
\[x_1 = \frac{-b + \sqrt{b^2 - 4ac}}{2a} \quad \text{and} \quad x_2 = \frac{-b - \sqrt{b^2 - 4ac}}{2a}\]This may fail in two ways:
- if a is 0 (divide by 0 is undefined);
- if the expression that square root is applied to is negative (and we insist on only real-valued solutions).
Therefore, let’s define a little library of math functions which encapsulate the possibility of failure in a Maybe
:
safeDiv :: Float -> Float -> Maybe Float
safeDiv _ 0 = Nothing
safeDiv numerator denominator = Just $ numerator / denominator
safeSqrt :: Float -> Maybe Float
safeSqrt x
| x < 0 = Nothing
| otherwise = Just $ sqrt x -- the built-in square root function
Great! Now we can use case
and pattern matchingA mechanism in functional programming languages to check a value against a pattern and to deconstruct data.
to make a safe solver of quadratic equations:
safeSolve :: Float -> Float -> Float -> Maybe (Float, Float)
safeSolve a b c =
case safeSqrt $ b*b - 4 * a * c of
Just s ->
let x1 = safeDiv (-b + s) (2*a)
x2 = safeDiv (-b - s) (2*a)
in case (x1,x2) of
(Just x1', Just x2') -> Just (x1',x2')
_ -> Nothing
Nothing -> Nothing
> safeSolve 1 3 2
Just (-1.0,-2.0)
> safeSolve 1 1 2
Nothing
Actually, not so great, we are having to unpack MaybesA built-in type in Haskell used to represent optional values, allowing functions to return either Just a value or Nothing to handle cases where no value is available.
multiple times, leading to nested case
s. This is just two levels of nesting; what happens if we need to work in additional computations that can fail?
The general problem is that we need to chain multiple functions of the form Float -> Maybe Float
. Let’s look again at the type of bindthe defining function which all monads must implement.
:
> :t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b
The first argument it expects is a value in a context m a
. What if that we apply it to a Maybe Float
?
> x = 1::Float
> :t (Just x>>=)
(Just x>>=) :: (Float -> Maybe b) -> Maybe b
So GHCi is telling us that the next argument has to be a function that takes a Float
as input, and gives back anything in a Maybe
. Our safeSqrt
definitely fits this description, as does safeDiv
partially applied to a Float
. So, here’s a safeSolve
which uses (>>=)
to remove the need for case
s:
safeSolve :: Float -> Float -> Float -> Maybe (Float, Float)
safeSolve a b c =
safeSqrt (b*b - 4 * a * c) >>= \s ->
safeDiv (-b + s) (2*a) >>= \x1 ->
safeDiv (-b - s) (2*a) >>= \x2 ->
pure (x1,x2)
> safeSolve 1 3 2
Just (-1.0,-2.0)
> safeSolve 1 1 2
Nothing
Note that Haskell has a special notation for such multi-line use of bindthe defining function which all monads must implement.
, called “do
notation”. The above code in a do
block looks like:
safeSolve a b c = do
s <- safeSqrt (b*b - 4 * a * c)
x1 <- safeDiv (-b + s) (2*a)
x2 <- safeDiv (-b - s) (2*a)
pure (x1,x2)
So inside a do
-block y<-x
is completely equivalent to x >>= \y -> …
, where in both cases the variable y
is in scope for the rest of the expression. We’ll see more explanation and examples of do
notation below.
How is a Nothing
result from either of our safe
functions handled? Well, the MaybeA built-in type in Haskell used to represent optional values, allowing functions to return either Just a value or Nothing to handle cases where no value is available.
instance of MonadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
defines bindthe defining function which all monads must implement.
like so:
instance Monad Maybe where
(Just x) >>= k = k x
Nothing >>= _ = Nothing
Meaning that anything on the right-hand side of a Nothing>>=
will be left unevaluated and Nothing
returned.
So that’s one instance of Monad
; let’s look at some more…
IO
The Haskell type which captures Input/Output effects is called IO
. As we demonstrated with the traverse
function, it is possible to perform IO
actions using fmap
(<$>
) and applicativeA type class in Haskell that extends Functor, allowing functions that are within a context to be applied to values that are also within a context. Applicative defines the functions pure and (<*>).
(<*>
)—for example printing to the console. The challenge is taking values out of an IO
context and using them to create further IO
effects.
Here are some simple IO
“actions”:
sayHi :: IO ()
sayHi = putStrLn "Hi, what’s your name?"
readName :: IO String
readName = getLine
greet :: String -> IO ()
greet name = putStrLn ("Nice to meet you " ++ name ++ "!")
The following typechecks:
main = greet <$> readName
When you run it from either GHCi or an executable compiled with ghc, it will pause and wait for input, but you will not see the subsequent greeting. This is because the type of the expression is:
> :t greet <$> readName
greet <$> readName :: IO (IO ())
The IO
action we want (greet
) is nested inside another IO
action. When it is run, only the outer IO
action is actually executed. The inner IO
computation (action) is not evaluated.
To see an output we somehow need to flatten the IO (IO ())
into just a single level: IO ()
.
(>>=)
gives us this ability:
> :t readName >>= greet
readName >>= greet :: IO ()
> readName >>= greet
Tim
Nice to meet you Tim!
The special case of bindthe defining function which all monads must implement.
(>>)
allows us to chain actions without passing through a value:
> :t (>>)
(>>) :: Monad m => m a -> m b -> m b
> sayHi >> readName >>= greet
Hi, what’s your name?
Tim
Nice to meet you Tim!
Do notation
Haskell gives us syntactic sugar for bindthe defining function which all monads must implement. in the form of “do blocks”:
main :: IO ()
main = do
sayHi
name <- readName
greet name
Which is entirely equivalent to the above code, or more explicitly:
main =
sayHi >>
readName >>=
\name -> greet name
Note that although <-
looks like assignment to a variable name
, it actually expands to a parameter name for a lambda expression following the bindthe defining function which all monads must implement.
. Thus, the way I read the line with the <-
in the following do expression:
do
name <- readName
greet name
is:
Take the value (a String
in this case)
out of the MonadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
context resulting from the expression on the right-hand side of the <-
(i.e. readName
) and assign it to the symbol on the left-hand side (i.e. name
) which remains in scope until the end of the do
block:
You can also mix in variable assignments from pure expressions using let:
do
name <- readName
let greeting = "Hello " ++ name
putStrLn greeting
Join
A function called “join
” from Control.Monad
also distills the essence of Monad
nicely. Its type and definition in terms of bindthe defining function which all monads must implement.
is:
join :: Monad m => m (m a) -> m a
join = (>>= id)
We can use join
to flatten nested Maybe
s:
>>> join (Just Nothing)
Nothing
>>> join (Just (Just 7))
Just 7
We can apply join to “flatten” the nested IO
contexts from the earlier fmap
example:
> :t join $ greet <$> readName
join $ greet <$> readName :: IO ()
Which will now execute as expected:
join $ greet <$> readName
Tim
Nice to meet you Tim!
List
As with the ApplicativeA type class in Haskell that extends Functor, allowing functions that are within a context to be applied to values that are also within a context. Applicative defines the functions pure and (<*>). list instance, the list implementation of bindthe defining function which all monads must implement. is defined with comprehension syntaxThe set of rules that defines the combinations of symbols that are considered to be correctly structured statements or expressions in a computer language. :
instance Monad [] where
xs >>= f = [y | x <- xs, y <- f x]
Where xs
is a list and f
is a function which returns a list. f
is applied to each element of xs
and the result concatenated. Actually, list comprehensions are just syntactic sugar for the list monadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
do
notation, for example, [(i,j)|i<-['a'..'d'],j<-[1..4]]
is equivalent to:
do
i <- ['a'..'d']
j <- [1..4]
pure (i,j)
[(‘a’,1),(‘a’,2),(‘a’,3),(‘a’,4),(‘b’,1),(‘b’,2),(‘b’,3),(‘b’,4),(‘c’,1),(‘c’,2),(‘c’,3),(‘c’,4),(‘d’,1),(‘d’,2),(‘d’,3),(‘d’,4)]
Which is itself syntactic sugar for:
['a'..'d'] >>= \i -> [1..4] >>= \j -> pure (i,j)
List comprehensions can also include conditional expressions which must evaluate to true for terms to be included in the list result. For example, we can limit the above comprehension to only pairs with even j
:
[(i,j) | i<-['a'..'d'], j<-[1..4], j `mod` 2 == 0]
This comprehension syntaxThe set of rules that defines the combinations of symbols that are considered to be correctly structured statements or expressions in a computer language.
desugars to a do
-block using the guard
function from Control.Monad
like so:
import Control.Monad (guard)
do
i <- ['a'..'d']
j <- [1..4]
guard $ j `mod` 2 == 0
pure (i,j)
[(‘a’,2),(‘a’,4),(‘b’,2),(‘b’,4),(‘c’,2),(‘c’,4),(‘d’,2),(‘d’,4)]
Our friend join
in the list MonadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
is simply concatenation:
>>> join [[1, 2, 3], [1, 2]]
[1,2,3,1,2]
You can think of the way that results in the List monadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
are chained as being a logical extension of the way Maybe
monadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
operations are chained. That is, a Maybe
returns either zero or one result, while a list returns an arbitrary number of results. The results of two list operations chained with (>>=)
is the cartesian product in a flat list.
Function
We saw previously that functions are instances of Functor
, such that fmap = (.)
. We also saw that functions are Applicative
such that a binary function (such as (+)
) can be lifted over multiple functions that need to be applied to the same argument, e.g.:
totalMark :: Student -> Int
totalMark = liftA2 (+) exam nonExam
So it shouldn’t really be any surprise that functions of the same input type can also be composed with monadic bindthe defining function which all monads must implement.
.
The right-to-left bindthe defining function which all monads must implement.
(=<<)
takes a binary function f
and a unary function g
and
creates a new unary function.
The new function will apply g
to its argument, then give the result as well as the
original argument to f
.
OK, that might seem a bit esoteric, but it lets us achieve some nifty things.
For example, below we compute (3*2) + 3
, but we did it by using the argument 3
in two different functions without explicitly passing it to either!
>>> ((+) =<< (*2)) 3
9
You can imagine a situation where you need to chain together a bunch of functions, but they all take a common parameter, e.g. a line break character.
greet linebreak = "Dear Gentleperson,"++linebreak
body sofar linebreak = sofar ++ linebreak ++ "It has come to my attention that… " ++ linebreak
signoff sofar linebreak = sofar ++ linebreak ++ "Your’s truly," ++ linebreak ++ "Tim" ++ linebreak
putStrLn $ (greet >>= body >>= signoff) "\r\n"
Dear Gentleperson,
It has come to my attention that…
Your’s truly,
Tim
In the next example we use the argument 3
in three different functions without passing it directly to any of them.
Note the pattern is that the right-most function is unary (taking only the specified argument), and subsequent functions in the chain are binary, their first argument being the result of the previous function application, and the second argument being the given 3
.
>>> ((*) =<< (-) =<< (2*)) 3
9
We can use the flipped bindthe defining function which all monads must implement. so it can be read left-to-right, if that’s more your thing:
>>> ((2*) >>= (-) >>= (*)) 3
9
The join
function passes one argument to a binary function twice which can be a useful trick:
>>> (join (,)) 3
(3,3)
>>> (join (+)) 3
6
Returning To Point Free
The very observant reader may recognize above construct of passing one argument to a binary function twice. We previously called this apply
, when discussing Function instances for applicativesA type class in Haskell that extends Functor, allowing functions that are within a context to be applied to values that are also within a context. Applicative defines the functions pure and (<*>).
. This can be a very useful pattern when making code point free.
We previously gave you an exercise, and labeled it as a scary extension, but now with more tools, we can make this much less scary:
f a b = a*a + b*b
First, lets use join to apply the binary function (*
) to the same argument twice
f :: Num a => a -> a -> a
f a b = (join (*) a) + (join (*) b)
One function, you may have been introduced to in your travels is on
:
on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
The on
function in Haskell takes a binary function, a unary function, and two arguments. It first applies the unary function to each argument separately, and then applies the binary function to the results of these applications. Using some operator sectioningThe process of partially applying an infix operator in Haskell by specifying one of its arguments. For example, (+1) is a section of the addition operator with 1 as the second argument.
, we can see our code fits exactly that pattern.
f a b = (+) ((join (*)) a) ((join (*)) b)
f a b = on (+) (join (*)) a b
We can now do two rounds of eta-conversion to make our code point free.
f = on (+) (join (*))
By convention, the on
function is normally written as an infix operation, i.e., surrounded by backticks
f :: Num a => a -> a -> a
f = (+) `on` join (*)
This is quite a common pattern in Haskell code, where this code says we apply the +
operation, after applying join (*)
(multiplying by itself) to each argument.
Looping with Monadic Effects
There are also various functions in Control.Monad
for looping functions with monadic effectsOperations that produce side effects and are managed within a monadic context, ensuring that the effects are sequenced and controlled.
(functions that return a result inside a MonadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
) over containers that are Foldable
and Traversable
.
First there’s mapM
which is effectively the same as traverse
(but requires the function to have a monadic effect, not just applicativeA type class in Haskell that extends Functor, allowing functions that are within a context to be applied to values that are also within a context. Applicative defines the functions pure and (<*>).
):
doubleIfNotBig n = if n < 3 then Just (n+n) else Nothing
>>> mapM doubleIfNotBig [1,2,3,4]
Nothing
>>> mapM doubleIfNotBig [1,2,1,2]
Just [2,4,2,4]
Such monadic looping functions also have versions with a trailing _
in their name, which throw away the actual results computed and just accumulate the effect (internally they use >>
instead of >>=
):
>>> mapM_ doubleIfNotBig [1,2,3,4]
Nothing
>>> mapM_ doubleIfNotBig [1,2,1,2]
Just ()
For folks who have been missing imperativeImperative programs are a sequence of statements that change a programs state. This is probably the dominant paradigm for programming languages today. Languages from Assembler to Python are built around this concept and most modern languages still allow you to program in this style.
for loops, there is a flipped version of mapM_
, called forM_
:
>>> forM_ ["It","looks","like","JavaScript!"] putStrLn
It
looks
like
JavaScript!
And of course we can fold using functions with Monadic effectsOperations that produce side effects and are managed within a monadic context, ensuring that the effects are sequenced and controlled. :
small acc x
| x < 10 = Just (acc + x)
| otherwise = Nothing
>>> foldM small 0 [1..9]
Just 45
>>> foldM small 0 [1..100]
Nothing
Conclusion
MonadsA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
really round out Haskell, making it a very powerful language with elegant ways to abstract common programming patterns. So far, we have looked at the Maybe
, IO
, and List
monadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
instances. The Maybe
monadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
allowed us to chain operations which may fail (in a more principled way than exception handling); IO
allowed us to chain operations which perform input and output; and the List
instance of monadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
allows us to sequence operations that may have multiple results (flattening the cartesian product of the results).
We’ll see MonadsA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
at work again in the next chapter when we build more sophisticated parserA function or program that interprets structured input, often used to convert strings into data structures.
combinatorsA higher-order function that uses only function application and earlier defined combinators to define a result from its arguments.
. Additionally, here’s a discussion about how to thread state such as random seeds through functions using a custom monadic context which serves as an introduction to the builtin State
monadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
.
With everything we’ve covered so far you should now be empowered to go out and write real-world programs. A slightly more advanced topic which you would soon encounter in the wild would be working within multiple monadic contexts at once. The most standard way to do this is using MonadA type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context. Transformers, but there are other approaches emerging, such as algebraic effects libraries. We’ll leave these for future self exploration though.
Glossary
Monad: A type class in Haskell that represents computations as a series of steps. It provides the bind operation (»=) to chain operations and the return (or pure) function to inject values into the monadic context.
Do Notation: A syntactic sugar in Haskell for chaining monadic operations. It makes the code more readable by hiding the explicit use of bind (»=).
Monadic Effects: Operations that produce side effects and are managed within a monadic context, ensuring that the effects are sequenced and controlled.
bind: the defining function which all monads must implement.