One of the simplest monads that we can craft is a state-passing monad. In Haskell, all state information usually must be passed to functions explicitly as arguments. Using monads, we can effectively hide some state information.
Suppose we have a function fof type a→b, and we need to add state to this function. In general, if state is of typestate, we can encode it by changing the type of
ftoa→state→(state,b). That is, the new version offtakes the original parameter of typeaand a new state parameter. And, in addition to returning the value of typeb, it also returns an updated state, encoded in a tuple.
For instance, suppose we have a binary tree defined as:
data Tree a = Leaf a
| Branch (Tree a) (Tree a)
Now, we can write a simple map function to apply some function to each value in the leaves:
mapTree :: (a -> b) -> Tree a -> Tree b mapTree f (Leaf a) = Leaf (f a)
9.3. A SIMPLE STATE MONAD 125
mapTree f (Branch lhs rhs) =
Branch (mapTree f lhs) (mapTree f rhs)
This works fine until we need to write a function that numbers the leaves left to right. In a sense, we need to add state, which keeps track of how many leaves we’ve numbered so far, to themapTreefunction. We can augment the function to something like:
mapTreeState :: (a -> state -> (state, b)) -> Tree a -> state -> (state, Tree b) mapTreeState f (Leaf a) state =
let (state’, b) = f a state in (state’, Leaf b)
mapTreeState f (Branch lhs rhs) state =
let (state’ , lhs’) = mapTreeState f lhs state (state’’, rhs’) = mapTreeState f rhs state’ in (state’’, Branch lhs’ rhs’)
This is beginning to get a bit unweildy, and the type signature is getting harder and harder to understand. What we want to do is abstract away the state passing part. That is, the differences betweenmapTreeandmapTreeStateare: (1) the augmentedf
type, (2) we replaced the type-> Tree bwith-> state -> (state, Tree b). Notice that both types changed in exactly the same way. We can abstract this away with a type synonym declaration:
type State st a = st -> (st, a)
To go along with this type, we write two functions:
returnState :: a -> State st a returnState a = \st -> (st, a)
bindState :: State st a -> (a -> State st b) -> State st b
bindState m k = \st -> let (st’, a) = m st
m’ = k a in m’ st’
Let’s examine each of these in turn. The first function,returnState, takes a value of typeaand creates something of typeState st a. If we think of thest
as the state, and the value of typeaas the value, then this is a function that doesn’t change the state and returns the valuea.
ThebindStatefunction looks distinctly like the interior let declarations inmapTreeState. It takes two arguments. The first argument is an action that returns something of type
awith statest. The second is a function that takes thisaand produces something of typebalso with the same state. The result ofbindStateis essentially the result of transforming theainto ab.
The definition ofbindStatetakes an initial state,st. It first applies this to the
State st aargument calledm. This gives back a new statest’and a valuea. It then lets the functionkact ona, producing something of typeState st b, called
m’. We finally runm’with the new statest’.
We write a new function,mapTreeStateMand give it the type:
mapTreeStateM :: (a -> State st b) -> Tree a -> State st (Tree b)
Using these “plumbing” functions (returnState and bindState) we can write this function without ever having to explicitly talk about the state:
mapTreeStateM f (Leaf a) = f a ‘bindState‘ \b -> returnState (Leaf b) mapTreeStateM f (Branch lhs rhs) = mapTreeStateM f lhs ‘bindState‘ \lhs’ -> mapTreeStateM f rhs ‘bindState‘ \rhs’ -> returnState (Branch lhs’ rhs’)
In theLeafcase, we applyftoaand then bind the result to a function that takes the result and returns aLeafwith the new value.
In theBranchcase, we recurse on the left-hand-side, binding the result to a func- tion that recurses on the right-hand-side, binding that to a simple function that returns the newly createdBranch.
As you have probably guessed by this point,State stis a monad,returnState
is analogous to the overloadedreturnmethod, andbindStateis analogous to the overloaded>>=method. In fact, we can verify thatState st aobeys the monad laws:
Law 1 states: return a >>= f≡f a. Let’s calculate on the left hand side, substituting our names:
returnState a ‘bindState‘ f ==> \st -> let (st’, a) = (returnState a) st m’ = f a in m’ st’ ==> \st -> let (st’, a) = (\st -> (st, a)) st in (f a) st’ ==> \st -> let (st’, a) = (st, a) in (f a) st’
9.3. A SIMPLE STATE MONAD 127
==>
\st -> (f a) st ==>
f a
In the first step, we simply substitute the definition ofbindState. In the second step, we simplify the last two lines and substitute the definition ofreturnState. In the third step, we applystto the lambda function. In the fourth step, we renamest’
tostand remove the let. In the last step, we eta reduce.
Moving on to Law 2, we need to show thatf >>= return≡f. This is shown as follows: f ‘bindState‘ returnState ==> \st -> let (st’, a) = f st in (returnState a) st’ ==> \st -> let (st’, a) = f st in (\st -> (st, a)) st’ ==> \st -> let (st’, a) = f st in (st’, a) ==> \st -> f st ==> f
Finally, we need to show thatStateobeys the third law: f >>= (\x -> g x >>= h)≡(f >>= g) >>= h. This is much more involved to show, so we will only sketch the proof here. Notice that we can write the left-hand-side as:
\st -> let (st’, a) = f st in (\x -> g x ‘bindState‘ h) a st’ ==> \st -> let (st’, a) = f st in (g a ‘bindState‘ h) st’ ==> \st -> let (st’, a) = f st in (\st’ -> let (st’’, b) = g a in h b st’’) st’ ==> \st -> let (st’ , a) = f st (st’’, b) = g a st’ (st’’’,c) = h b st’’ in (st’’’,c)
The interesting thing to note here is that we have both action applications on the same let level. Since let is associative, this means that we can put whichever bracketing we prefer and the results will not change. Of course, this is an informal, “hand waving” argument and it would take us a few more derivations to actually prove, but this gives the general idea.
Now that we know thatState stis actually a monad, we’d like to make it an instance of theMonad class. Unfortunately, the straightforward way of doing this doesn’t work. We can’t write:
instance Monad (State st) where { ... }
This is because you cannot make instances out of non-fully-applied type synonyms. Instead, what we need to do instead is convert the type synonym into a newtype, as:
newtype State st a = State (st -> (st, a))
Unfortunately, this means that we need to do some packing and unpacking of the
Stateconstructor in theMonadinstance declaration, but it’s not terribly difficult:
instance Monad (State state) where
return a = State (\state -> (state, a)) State run >>= action = State run’
where run’ st =
let (st’, a) = run st State run’’ = action a in run’’ st’
Now, we can write ourmapTreeMfunction as:
mapTreeM
mapTreeM :: (a -> State state b) -> Tree a -> State state (Tree b)
mapTreeM f (Leaf a) = do b <- f a return (Leaf b) mapTreeM f (Branch lhs rhs) = do lhs’ <- mapTreeM f lhs rhs’ <- mapTreeM f rhs return (Branch lhs’ rhs’)
which is significantly cleaner than before. In fact, if we remove the type signature, we get the more general type:
mapTreeM :: Monad m => (a -> m b) -> Tree a -> m (Tree b)
9.3. A SIMPLE STATE MONAD 129 That is,mapTreeMcan be run in any monad, not just ourStatemonad. Now, the nice thing about encapsulating the stateful aspect of the computation like this is that we can provide functions to get and change the current state. These look
like: getState
putState
getState :: State state state
getState = State (\state -> (state, state)) putState :: state -> State state ()
putState new = State (\_ -> (new, ()))
Here, getState is a monadic operation that takes the current state, passes it through unchanged, and then returns it as the value. TheputStatefunction takes a new state and produces an action that ignores the current state and inserts the new one.
Now, we can write ournumberTreefunction as:
numberTree :: Tree a -> State Int (Tree (a, Int)) numberTree tree = mapTreeM number tree
where number v = do cur <- getState putState (cur+1) return (v,cur)
Finally, we need to be able to run the action by providing an initial state:
runStateM :: State state a -> state -> a runStateM (State f) st = snd (f st)
Now, we can provide an example Tree:
testTree = Branch (Branch (Leaf ’a’) (Branch (Leaf ’b’) (Leaf ’c’))) (Branch (Leaf ’d’) (Leaf ’e’))
and number it:
State> runStateM (numberTree testTree) 1
Branch (Branch (Leaf (’a’,1)) (Branch (Leaf (’b’,2)) (Leaf (’c’,3)))) (Branch (Leaf (’d’,4))
This may seem like a large amount of work to do something simple. However, note the new power ofmapTreeM. We can also print out the leaves of the tree in a left-to-right fashion as:
State> mapTreeM print testTree ’a’
’b’ ’c’ ’d’ ’e’
This crucially relies on the fact thatmapTreeMhas the more general type involving arbitrary monads – not just the state monad. Furthermore, we can write an action that will make each leaf value equal to its old value as well as all the values preceeding:
fluffLeaves tree = mapTreeM fluff tree where fluff v = do
cur <- getState putState (v:cur) return (v:cur)
and can see it in action:
State> runStateM (fluffLeaves testTree) [] Branch (Branch (Leaf "a") (Branch (Leaf "ba")
(Leaf "cba"))) (Branch (Leaf "dcba") (Leaf "edcba"))
In fact, you don’t even need to write your own monad instance and datatype. All this is built in to theControl.Monad.Statemodule. There, ourrunStateM
is calledevalState; ourgetStateis calledget; and ourputStateis called
put.
This module also contains a state transformer monad, which we will discuss in Section 9.7.