Class Monad
Class Monad
is defined as follows:
-- from Control.Monad that comes with GHC
class Monad m where
-- | Sequentially compose two actions, passing any value produced
-- by the first as an argument to the second.
(>>=) :: forall a b. m a -> (a -> m b) -> m b
-- | Sequentially compose two actions, discarding any value produced
-- by the first, like sequencing operators (such as the semicolon)
-- in imperative languages.
(>>) :: forall a b. m a -> m b -> m b
-- Explicit for-alls so that we know what order to
-- give type arguments when desugaring
-- | Inject a value into the monadic type.
return :: a -> m a
-- | Fail with a message. This operation is not part of the
-- mathematical definition of a monad, but is invoked on pattern-match
-- failure in a @do@ expression.
fail :: String -> m a
{-# INLINE (>>) #-}
m >> k = m >>= \_ -> k
fail s = error s
The two key functions are >>=
and return
. Note that >>
, which sequences two actions, is given a default definition at the bottom. Instances of Monad
don't have to implement >>
if that default definition is okay, which it usually is.
Reminder about do notation
stuff x y z = do
a <- anAction x
b <- anotherAction y z
let c = nifty a b
d <- if c == 0
then narfle a
else burble b
return (a,b,c,d)
translates into
stuff x y z =
anAction x >>= (\a ->
anotherAction y z >>= (\b ->
let
c = nifty a b
in (if c == 0 then narfle a else burble b) >>= (\d ->
return (a,b,c,d)
) -- end of \d -> ...
) -- end of \b -> ...
) -- end of \a -> ...
What return does and doesn't do
We've seen the IO
monad's bind operator already:
instance Monad IO where
action1 >>= action2 = ...
-- perform action1, yielding a result x
-- perform (action2 x)
...
but return
is new. Note the type, return :: a -> m a
, so for the IO
monad, what return
does is produce x
, sort of as if it had been read from a file or some other input source, but without actually doing anything to the real world. Here's one reason you might need return
:
analyzeFile :: FilePath -> IO String
analyzeFile path = do
h <- openFile path ReadMode
result <- ...
hClose h
return result
main = do
result <- analyzeFile "/etc/stuff"
...
That is, you want to output something from an action, but you have to perform other actions after the construction of the result but before you consider your action complete. Note that unlike in Python, C, C++, Java, and so many other languages, return
is not a keyword, it doesn't generally cause exectution to exit from a procedure early, and you don't need it for a function that isn't a monadic action.
About the unit type and value ()
Haskell functions have to result in some value. There is no void
as in C and Java. However, sometimes an action has nothing useful to produce. Haskell's convention is the unit type ()
which suggests a 0-element tuple. The type of ()
is also called ()
. Here's a standard monadic conditional function:
when :: Monad m => Bool -> m () -> m ()
when t action =
if t
then action
else return ()
If the given test value t
is True
, perform the action, otherwise, do nothing. And since the result of when
may be an action that does nothing and produces nothing, the action you pass it to do when t == True
must also produce nothing. (Both branches of an if
must have the same type.)
Famous monad
What follows is a rough sketch of several other monads that are built in to the Haskell standard library. They aren't exactly as I've presented here: I've left out a lot of complications because this article is about the concepts rather covering every tiny nasty little detail. Look at the modules Control.Monad.*
for full details and references to research papers and other articles.
The State monad
Threading state
Since you generally create new data structures rather than modifying them, it's common to have stuff like this in Haskell:
doTheWork x y z =
let
thing0 = ...
(a, thing1) = doStuff x thing0
(b, thing2) = doMoreStuff y thing1
(c, thing3) = doEvenMoreStuff z thing2
in
(..., thing3)
Common examples include adding things to a Data.Set
or other container, and random number generation.
An easy random number generator
So, let's write a linear congruential generator. This isn't an especially good generator--Mersenne twister would be better for example--but it'll serve as an example.
import Data.Word
data LCGState = LCGState Word32 Word32 Word32 Word32
deriving (Read, Show, Eq, Ord)
-- This is apparently what glibc uses
makeSeed x = LCGState (2 ^ 31) 1103515245 12345 x
nextState :: LCGState -> LCGState
nextState (LCGState m a c x1) = LCGState m a c x2
where x2 = (a * x1 + c) `mod` m
-- | Pull a random number between 0 and 1
nextRandomDouble :: LCGState -> (Double, LCGState)
nextRandomDouble state0 =
let
LCGState m a c x = state0
state1 = nextState state0
in
((fromIntegral x) / (fromIntegral m), state1)
s0 = makeSeed 6502
(y1, s1) = nextRandomDouble s0
(y2, s2) = nextRandomDouble s1
(y3, s3) = nextRandomDouble s2
main = do
print s0
print [y1, y2, y3]
It would be nice not to have to keep threading the random seed through all those calculations. It turns out that this type of calculation is handled nicely as a state monad. We want to end up writing stuff like this:
pickThree = do
y1 <- pullRandomDouble
y2 <- pullRandomDouble
y3 <- pullRandomDouble
return [y1, y2, y3]
Implementing a state monad for the random generator
This is how to implement a state monad:
-- This is a wrapper around a transition function
-- that takes a s(tate) and returns a r(esult) and a new s(tate)
data State s r = State (s -> (r, s))
-- Running a state object on an s is easy, just apply the wrapped function
runState (State f) s = f s
instance Monad (State s) where
-- introduce the result without modifying s
return result = State (\s -> (result, s))
-- compose two transition functions,
-- threading the result of the first into the second
(State fTrans) >>= mkTrans = State hTrans
where
hTrans s0 =
let
(r1, s1) = fTrans s0
State gTrans = mkTrans r1
(r2, s2) = gTrans s1
in
(r2, s2)
So now we can do random number generation like this:
import Data.Word
data LCGState = LCGState Word32 Word32 Word32 Word32
deriving (Read, Show, Eq, Ord)
-- This is apparently what glibc uses
makeSeed x = LCGState (2 ^ 31) 1103515245 12345 x
nextState :: LCGState -> LCGState
nextState (LCGState m a c x1) = LCGState m a c x2
where x2 = (a * x1 + c) `mod` m
-- | Pull a random number between 0 and 1
nextRandomDouble :: LCGState -> (Double, LCGState)
nextRandomDouble state0 =
let
LCGState m a c x = state0
state1 = nextState state0
in
((fromIntegral x) / (fromIntegral m), state1)
-- This is a wrapper around a transition function
-- that takes a s(tate) and returns a r(esult) and a new s(tate)
data State s r = State (s -> (r, s))
-- Running a state object on an s is easy, just apply the wrapped function
runState (State f) s = f s
instance Monad (State s) where
-- introduce the result without modifying s
return result = State (\s -> (result, s))
-- compose two transition functions,
-- threading the result of the first into the second
(State fTrans) >>= mkTrans = State hTrans
where
hTrans s0 =
let
(r1, s1) = fTrans s0
State gTrans = mkTrans r1
(r2, s2) = gTrans s1
in
(r2, s2)
pullRandomDouble :: State LCGState Double
pullRandomDouble = State nextRandomDouble
pickThree :: State LCGState [Double]
pickThree = do
y1 <- pullRandomDouble
y2 <- pullRandomDouble
y3 <- pullRandomDouble
return [y1, y2, y3]
s0 = makeSeed 6502
(ys, sNext) = runState pickThree s0
main = do
print ys
The GHC library includes an implementation of the state monad very much like the one here. See the Control.Monad.State
module.
Exercises
Use this workspace:
--show Imports
import Data.Word
import Control.Monad
--/show
--show Random number generator
data LCGState = LCGState Word32 Word32 Word32 Word32
deriving (Read, Show, Eq, Ord)
-- This is apparently what glibc uses
makeSeed x = LCGState (2 ^ 31) 1103515245 12345 x
nextState :: LCGState -> LCGState
nextState (LCGState m a c x1) = LCGState m a c x2
where x2 = (a * x1 + c) `mod` m
-- | Pull a random number between 0 and 1
nextRandomDouble :: LCGState -> (Double, LCGState)
nextRandomDouble state0 =
let
LCGState m a c x = state0
state1 = nextState state0
in
((fromIntegral x) / (fromIntegral m), state1)
--/show
--show State monad
-- This is a wrapper around a transition function
-- that takes a s(tate) and returns a r(esult) and a new s(tate)
data State s r = State (s -> (r, s))
-- Running a state object on an s is easy, just apply the wrapped function
runState (State f) s = f s
instance Monad (State s) where
-- introduce the result without modifying s
return result = State (\s -> (result, s))
-- compose two transition functions,
-- threading the result of the first into the second
(State fTrans) >>= mkTrans = State hTrans
where
hTrans s0 =
let
(r1, s1) = fTrans s0
State gTrans = mkTrans r1
(r2, s2) = gTrans s1
in
(r2, s2)
--/show
--show More friendly random number generation
-- | Generate a random number uniformly between 0 and 1
pullRandomDouble :: State LCGState Double
pullRandomDouble = State nextRandomDouble
-- Your job, see below:
pullRandomBool = undefined
pullDieToss = undefined
pullDieTosses = undefined
--/show
--show Test cases
pickThree :: State LCGState [Double]
pickThree = do
y1 <- pullRandomDouble
y2 <- pullRandomDouble
y3 <- pullRandomDouble
return [y1, y2, y3]
s0 = makeSeed 6502
(ys, sNext) = runState pickThree s0
main = do
print ys
--/show
Flip a coin (Bernouli random variables)
Write a monadic function pullRandomBool :: Double -> State LCGState Double
that takes a number q between 0 and 1 and generates True
with probability q, and False
with probability (1-q).
Toss a die
Write a monadic function pullDieToss :: Int -> State LCGState Int
that takes a number n (= number of sides on the die) and generates a random number between 1 and n, uniformly.
Many dice
Look up Control.Monad
on the Haskell web site. Look for a function called replicateM
. Use it to write a function pullDieTosses :: Int -> Int -> State LCGState [Int]
that takes a number k and a number n, and generates a list of k rolls of a die with n sides.
The Maybe monad
Recall that the Maybe
type represents something that might not exist, like a pointer that is allowed to be null:
data Maybe t = Nothing | Just t
Maybeness also has monadic semantics, and I like to think of the >>=
operator as 'feeding' a value to a function. Feeding Nothing
to any function results in Nothing
. Feeding Just x
to a function applies it to x
, and the result might be an actual value Just y
or Nothing
.
instance Monad Maybe where
return x = Just x
Nothing >>= f = Nothing
Just x >>= f = f x
Using Maybe
monadically is a clean way to write a sequence of operations that could fail (resulting in Nothing
) at any step.
You can also create an error monad, which is very similar but instead of Nothing
, you return something describing the failure. You can use that as a means of throwing and catching exceptions, although Haskell can also do that within the IO monad. See Control.Monad.Error
.
The List monad
A list has monadic sematics, too. If a Maybe t
is either one value Just x
or Nothing
, you can think of a list as zero or more possible values. Returning a value to the list monad produces a list of that one value. Binding a list of possible values to a function means applying that function to each possible value, and building one big list out of all the possible results it returns.
instance Monad [] where
return x = [x]
xs >>= f =
concat (map f xs)
And actually, the list comprehension notation is transformed into monadic binding during compilation:
ns = [1 .. 5]
squares = [n^2 | n <- ns]
squares2 = do
n <- ns
return (n^2)
-- all possible pairings of ns and squares:
pairs = [(n, m) | n <- ns, m <- squares]
-- all possible pairings of ns and squares:
pairs2 = do
n <- ns
m <- squares
return (n, m)
main = do
print ns
print squares
print squares2
print pairs
print pairs2
The Identity monad
Plain old function application is also a monad, called the identity monad. All we have to do is wrap it:
data Identity t = Identity t
instance Monad Identity where
return x = Identity x
(Identity x) >>= f = f x
Why would you ever both with that? Well...
Monad transformers
... it turns out there are ways to convert a monad into a monad transformer, which then adds properties to another monad. So, there's a StateT
that adds a state item to another monad. There's ErrorT
that adds exception handling capabilities. So in the Haskell standard libary, the plain old State
monad is defined by applying StateT
to Identity
and Error
is defined by applying ErrorT
to Identity
. There's also a ListT
that adds backtracking, ReaderT
, WriterT
, ... and you can combine them in all sorts of ways.
We don't have time for all of that here.
ST
The state thread monad ST
is kind of like State
: It performs sequences of operations that send state around, but the state itself is an abstraction that is never made available. In fact, it's essentially an abstraction of the entire machine memory. The use of ST
is more like IO
than Stat
: It provides a way of sequencing imperative-like operations, such as changing a data structure in place. However, you can run many ST
actions in any part of the program, and the actions aren't as strictly ordered as in IO
. There's normally only one sequence of IO
actions (the main
function). And you can peek at Control.Monad.ST
and see how GHC implements IO
in terms of ST
.
The weirdness of ST
is a feature called rank-n types. In this case it refers to a phantom unspecified type that represents the abstract world state to be threaded, and because of how rank-n types work, each call to runST
that executes a sequence of ST
actions gets its own abstract world state. This bit of madness is needed to make sure that, for example, a mutable array under construction inside runST
in one place can't be deallocated by another sequence of actions running inside another runST
. (Remember that Haskell can evaluate a program in any order, so it's allowed to do some work on one ST
action, then turn its attention to another.)