Grokking monads

It has been said that the world does not need yet another monad tutorial. Hal Daume III’s Yet Another Haskell Tutorial (copyright 2002 to 2006) is available as a Wikibook and there is a featured wikibook on Haskell itself. The November 2008 edition of Real World Haskell by Bryan O’Sullivan, Don Stewart, and John Goerzen is freely available online, as is Miran Lipovača’s Learn You a Haskell for Great Good! published in April 2011. However, although it is true that a good way to learn to use something is to use it, another good way is to try to teach it.

Values, types and classes

In Haskell, every value has a type (or, more strictly, an expression evaluates to a value and has a type). I think of types as sets of values. Functions map values of one type to values of another type, or the same type, and are themselves expressions. Some types are instances of type classes. Classes (for short) promise the availability of certain functionality, referenced by consistent names. If an instance of a class is well-behaved, the functionality delivered may also follow certain rules or ‘laws’.

Monad is the name of a class, for types that are instances of the Functor and Applicative classes – at least, since version 7.10.1 of the Glasgow Haskell Compiler (GHC) and the associated base-4.8.0.0 package. All these classes are exported by the Prelude. Many types that are commonly encountered are instances of the Monad class, including IO a, the type used for input and output (IO).

Semigroups and monoids

Before turning to monads, it is helpful to lay some other foundations. Module Data.Semigroup is part of package base. (Unless stated otherwise, all identified modules are part of that package.) The Semigroup class in that module promises, amongst other functionality, a function (an operator) (<>):

(Conventionally, a to z are used as type variables but here I use more descriptive ones: semigroup rather than, say, simply a.)

A well-behaved instance of the class follows the following rule (that is, the <> operator is intended to be an associative one):

x <> (y <> z) = (x <> y) <> z

The Monoid class in module Data.Monoid promises, amongst other functionality, a function mempty for types that are semigroups.

and a well-behaved instance of the class follows the rules below (that is, mempty is an identity element with respect to the <> operator):

x <> mempty = x
mempty <> x = x

For example, for all types a, the type list of type a, [a], is a monoid:

IO and RealWorld

Another foundation is the IO a type, defined in module GHC.Types of package ghc-prims as:

For all types a, IO a is a type that corresponds directly with the type of a function that maps a ‘state of the real world’ to a tuple (a pair) of another ‘state of the real world’ and a value of type a.

Identity

A final foundation is the type Identity t, defined in module Data.Functor.Identity.

Identity t is an instance of a number of classes. Its usefulness comes from it being an instance of the Monad class.

Functor

The starting point for monads is the Functor class exported by module Data.Functor (in fact, exported originally by GHC.Base). The class promises a function: fmap.

A well-behaved instance of the class follows the rules below:

fmap id = id
fmap (h . g) = (fmap h) . (fmap g)

Values of types that are functors ‘contain’ (or provide a ‘context’ or ‘wrapper’ for) one or more values of the same type or, in some cases, are an ’empty’ container or only context and no value. fmap maps a function over those contained values.

For example, the list type, [], which is easily seen as a ‘container’, is a functor:

(In fact, for [], fmap = map.)

Either error is also a functor:

as is the (,) first type:

The first value of the pair provides the ‘context’ for the second value.

The idea of ‘context’ can be more complex than that of a ‘container’. The (->) input type (the type of functions that map from type input) is also a functor:

Another more complex example of a functor is the following, which is of particular interest given the similarity between IO and StateProcessor state:

Values of type StateProcessor state value are referred to as ‘actions’. (StateProcessor is a descriptive name but cumbersome due to its length. The module Control.Monad.State.Lazy in package mtl defines type synonym State s a and function runState :: State s a -> s -> (a, s).)

Finally, Identity is a functor:

coerce :: Coercible type1 type2 => type1 -> type2 is exported by module Data.Coerce. Instances of class Coercible type1 type2 cannot be declared manually but exist if the compiler can infer that types type1 and type2 have the same representation. coerce converts a value of type type1 to a value of type type2. Here, coerce converts a function of type a -> b into the corresponding function of type Identity a -> Identity b.

Applicative

The next step towards monads is the Applicative class exported by module Control.Applicative (in fact, originally exported by GHC.Base). The class promises five functions for types that are functors. Two are: pure, and (<*>). The other three are: liftA2, (*>) and (<*).

A well-behaved instance of the Applicative class follows these rules:

(pure id) <*> x' = x'
(pure g) <*> (pure x) = pure (g x)
g' <*> (pure x) = pure ($ x) <*> g'
(((pure (.)) <*> h') <*> g') <*> x' = h' <*> (g' <*> x')

and also these:

liftA2 g x' y' = (fmap g x') <*> y' (true, by default)
x' *> y' = (fmap (const id) x') <*> y' (true, by default)
x' <* y' = (fmap const x') <*> y' (true, by default)

Only pure and one of (<*>) or liftA2 need to be described to define all of them as, also by default:

g' <*> x' = (liftA2 id) g' x'.

In that regard, the type of liftA2 is:

liftA2 :: (a -> b -> c) -> applicative a -> applicative b -> applicative c

As function arrows associate to the right, that is equivalent to:

liftA2 :: (a -> (b -> c)) -> applicative a -> applicative b -> applicative c

The type of id is, for all types t, id :: t -> t. In the case of liftA2, if id is its first argument, then types t and a must be b -> c, so that:

(liftA2 id) :: applicative (b -> c) -> applicative b -> applicative c

the type of (<*>).

As indicated by its type, pure :: a -< applicative a, applied to an expression puts (lifts) that expression into the ‘container’ or ‘context’.

From the rules above, it follows that:

(pure g) <*> x' = fmap g x'

Examples of familiar types that are applicative help to understand these relationships. The type [] is applicative:

So is Either error:

and so is the type (,) first, if first is a monoid:

The pure function applied to an expression lifts it into a pair where the ‘context’ of the first element is mempty.

The type (->) input is applicative:

The type StateProcessor state can also be applicative, as follows:

Finally, Identity is applicative:

Here, coerce converts a function of type Identity (a -> b) into the corresponding function of type Identity a -> Identity b.

Monad

Finally, we arrive at monads. The Monad class is exported by module Control.Monad (in fact, it is originally exported by GHC.Base). The class promises a new function (an operator) for types that are applicative, namely: (>>=) (read as bind). It also promises return, intended to be a synonym for pure; (>>) (read as then), intended to be a synonym for (*>); and (for now) fail (see class MonadFail).

A well-behaved instance of the Monad class follows these rules:

(pure x) >>= g = g x
x' >>= pure = x'
x' >>= (\x -> (g x) >>= h) = (x' >>= g) >>= h

[] is a monad:

Either error is a monad:

The type (,) first is a monad, if it is applicative and first is a monoid:

The type (->) input is a monad:

The type StateProcessor state can also be a monad, as follows:

An example of the use of that monad is set out below, for StateProcessor String (that is, strings represent the state):

The expression runStateProcessor main' "" evaluates as: ((),"Printing: Hi!;Getting input: dummy input;Printing: dummy input;"), showing how the monad functionality allows actions to be sequenced.

Finally, Identity is a monad, although (by design) a dull one:

do expressions

As explained in the Haskell 2010 Language Report, do expressions provide an alternative syntax for using the then (>>) and bind(>>=) operators.

Trivially, expression is equivalent to do { expression }.

expression >> do { statements } is equivalent to do { expression; statements }. The do syntax replaces then (>>) with a ‘semi-colon’.

let declarations in do { statements } is equivalent to do { let declarations; statements }.

Finally, let g pattern = do { statements } in expression >>= g is equivalent to do { pattern <- expression; statements } (ignoring, for the present purposes, if the pattern does not match the expression).

These equivalences are usually presented the other way around; how the do notation is translated into ‘raw’ syntax.

For example, the following expressions are all equivalent (the last using layout to remove the need for curly brackets and semi-colons):

Monad transformers

The transformers package provides a library of complex types and functions that are monad transformers – that is, they add a layer of additional functionality to another, underlying, monad. The library is portable – it does not use extensions to the Haskell 98 language. The mtl (Monad Transformers Library) package builds on transformers to provide additional functionality. However, the library is non-portable – it makes use of GHC extensions to the Haskell 2010 language.

For example, the module Control.Monad.Writer of the mtl exports type WriterT log monad' value, where type log is intended to be a monoid and type monad' is intended to be a underlying monad:

and function runWriterT:

As indicated above, WriterT log monad' is itself a monad.

MonadWriter, above, is a multi-parameter type class which promises function writer:

The functional dependency monad -> log identifies that the type log is determined by the type monad.

Class MonadTrans is exported by module Control.Monad.Trans.Class and promises function lift:

The simplest underlying monad that WriterT log can build upon is Identity, and module Control.Monad.Writer exports a type synonym:

and function runWriter :: Writer log value -> (value, log).

The mtl package provides other monad transformers, including:

  • ReaderT env (with runReaderT :: ReaderT env monad' value -> env -> monad' value). This gives read-only access to an environment;
  • StateT state (with runStateT :: StateT state monad' value -> state -> monad' (value, state)). This provides access to a state that can be changed; and
  • RWST env log state (with runRWST :: RWST env log state monad' value -> env -> state -> monad' (value, state, log)). This provides combined Reader, Writer and State functionality.