User:Davjam2:Example/StateMonad
Note
The text from this draft was copied into Haskell/Understanding_monads/State, and so I've deleted it from here.
This page now used for draft additions/amendments.
Handling Combined States[edit | edit source]
Suppose we wanted to create a random turnstile, where each visitor would be given a random turnstile input: either they insert a coin (but are not allowed through); or they get to push the arm (and go through if it opens, but are otherwise sent away).
Here's one useful bit of code:
randomInputS :: State StdGen TurnstileInput
randomInputS = do
b <- getRandomS
return $ if b then Coin else Push
This allows us to generate random turnstileInput
values[1]. However, our random turnstile machine needs to track both the state of a random number generator and the state of the turnstile. We want to write a function like this:
randomTurnS :: State (StdGen, TurnstileState) TurnstileOutput
And this function needs to call both randomInputS
(which is in the State StdGen
monad) and turnS
(which is in the State TurnstileState
monad).
Exercises |
---|
|
Much of the code in randomTurnS
deals with managing the state: accessing the combined state, unpacking subcomponents, forwarding them to the individual State monads, recombining them and putting the combined state back. The state management code is not too bad in this case, but could easily become cumbersome in a more complex function. And it is something we wanted the State
monad to hide from us.
State-Processing a Subcomponent[edit | edit source]
Ideally we'd want some utility function(s) that allow us to invoke a State StdGen
monad function (or State TurnstileState
monad function) from within a State (StdGen, TurnstileState)
monad function. These function(s) should take care of the state management for us, ensuring that the right subcomponent of the combined state is updated.
Here's one such a function that works for any combined state represented as a pair, and performs the state update on the fst of the pair:
processingFst :: State a o -> State (a,b) o
processingFst m = do
(s1,s2) <- get
let (o,s1') = runState m s1
put (s1',s2)
return o
Note the type:
GHCi> :t processingFst randomInputS
processingFst randomInputS :: State (StdGen, b) TurnstileInput
processingFst
"converts" a State
monad (in this case with state type StdGen
) to another State
monad (in this case with state type (StdGen, b)
, where b
can be any type, even a TurstileState
).
Exercises |
---|
|
Note how randomTurnS
is no longer directly involved in the details of the state management, and its business logic is much more apparent.
Generic Subcomponent Processing[edit | edit source]
We can see that processingFst
and processingSnd
are very similar. They both extract a subcomponent of a combined state, runState
on that subcomponent, then update the combined state with the new value of the subcomponent.
Let's combine them into a single generic subcomponent processing function. To do this, we could pass in separate parameters, one of type (cmb -> sub)
(a function that extracts a subcomponent from a combined state value), and another of type (cmb -> sub -> cmb)
(a function that, given a combined value and a new value for a subcomponent, returns the revised combined value with the updated subcomponent). However, it's a bit neater to package these two functions together in a type which we'll call Lens
:
data Lens cmb sub = Lens
{ view :: cmb -> sub,
set :: cmb -> sub -> cmb
}
We can provide specific lenses onto the fst and snd elements in a pair:
fstL :: Lens (a,b) a
fstL = Lens fst (\(_,y) x -> (x,y))
sndL :: Lens (a,b) b
sndL = Lens snd (\(x,_) y -> (x,y))
So now:
GHCi> view fstL ("fred", 5)
"fred"
GHCi> set fstL ("fred", 5) "sue"
("sue",5)
Note
Lenses that are more sophisticated and powerful are described later, but it's also harder to understand how they work. Our simple lenses are sufficient for now, but you might want to update the random turnstile code to use "proper lenses" later on.
We can now replace processingFst
and processingSnd
with our generic function.
Exercises |
---|
|
Our final random turnstile code is neater, with three separate logical functions segregated:
- state management (now in a single
processing
utility function, which can be reused elsewhere); - subcomponent accessing and update (using
Lens
. We could use a standard package for this, which also provides many more lenses, automatic creation of lenses on custom data types, easy combining of lenses for deeply-nested subcomponents, etc); and - the "business logic" of the turnstile, which is now very apparent.
In our first implementation, all three of these were muddled together.
Let's give it a go:
GHCi> g <- newStdGen
GHCi> evalState (replicateM 10 randomTurnS) (g, Locked)
[Thank,Open,Tut,Thank,Thank,Open,Tut,Tut,Tut,Thank]
I'm not sure we'll sell many of them, though.
Notes
- ↑ Alternatively, we could make
TurnstileInput
an instance ofUniform
, but this code seems easier.