First, we'll talk about the definition of StateFunc
in the library. Then we'll work through a second example with StateFunc
s.
The State
library in Control.Monad.State
provides the functionality we implemented in StateFunc
. Unlike our newtype
definition, the library version is defined using rather more exotic features that we have not seen yet:
type State s = StateT s Identity
Despite the differences in name and representation, keep thinking of values of type State s a
as "stateful functions that, when, run produce values of type a
."
The helper functions we defined for StateFunc
are also provided by the State
library, with appropriate renamings:
state :: (s -> (a,s)) -> State s a -- create a stateful comp.
get :: State s s -- get state out
put :: s -> State s () -- set "current" state
modify :: (s -> s) -> State s () -- modify the state
evalState :: State s a -> s -> a -- run and return final value
execState :: State s a -> s -> s -- run and return final state
Note that the definition of State
above is an alias, not a dataype; the library function state
builds State
values.
Also, beware that if you ask Haskell for the types of these functions, you'll get more exotic types:
> :t state -- MonadState s m => (s -> (a, s)) -> m a
> :t get -- MonadState s m => m s
> :t put -- MonadState s m => s -> m ()
> :t modify -- MonadState s m => (s -> s) -> m ()
Don't worry about this for now. Note that if we instantiate the m
type with State s
, these signatures match our understanding above.
> :t state :: (s -> (a, s)) -> State s a
> :t get :: State s s
> :t put :: s -> State s ()
> :t modify :: (s -> s) -> State s ()
> import System.Random
> :t random -- (RandomGen g, Random a) => g -> (a, g)
> :info Random -- describes types a that can take on random values
> :info RandomGen -- describes types g that can act as source of randomness
Okay, so how do we get a StdGen
?
> :t mkStdGen
> let g = mkStdGen 17
> :t random g
> :t fst $ random g
> fst $ random g -- Haskell thinks we want an Int
> fst $ random g :: Int
> fst $ random g :: Bool
> fst $ random g :: Float
Can we get multiple random booleans?
> fst $ random g :: Int
> fst $ random g :: Int
> fst $ random g :: Int
Need to thread the StdGen
s through...
threeInts_ :: StdGen -> ((Int,Int,Int), StdGen)
threeInts_ g0 =
let
(i1,g1) = random g0
(i2,g2) = random g1
(i3,g3) = random g2
in
((i1, i2, i3), g3)
Look familiar?!?
The type State StdGen a
describes (wrapped) functions of type StdGen -> (a, StdGen)
. We will end up writing State StdGen
over and over again, so we can choose to introduce an alias if we'd like.
type RandState a = State StdGen a
type RandState = State StdGen
Start by writing a computation that produces a single Int
.
oneInt :: State StdGen Int
oneInt :: RandState Int
oneInt = state $ \g0 ->
let (i1,g1) = random g0 in
(i1, g1)
Or simply:
oneInt = state random
Or, can rewrite with do
-notation. (Arguably more confusing, because random
takes StdGen
arg.)
oneInt = do
g0 <- get -- get >>= \g0 ->
let (b,g1) = random g0 -- let (b,g1) = random g0 in
put g1 -- put g1 >>
return b -- return b
Now we can easily sequence calls together.
threeInts :: State StdGen (Int, Int, Int)
threeInts :: RandState (Int, Int, Int)
threeInts =
oneInt >>= \i1 ->
oneInt >>= \i2 ->
oneInt >>= \i3 ->
return (i1,i2,i3)
Can rewrite with do
-notation.
threeInts = do
i1 <- oneInt
i2 <- oneInt
i3 <- oneInt
return (i1, i2, i3)
Okay, now how to run a RandState
computation from an init state? First cut is to take a StdGen
as input.
run_ :: RandState a -> StdGen -> a
run_ sb g = evalState sb g -- recall evalState
> run_ threeInts (mkStdGen 1)
> run_ threeInts (mkStdGen 2)
> run_ threeInts (mkStdGen 3)
But don't want to have to explicitly pick a StdGen
on each run. And anyway, how would be compute a random StdGen
? Want Haskell to (randomly) pick one for us.
> :t getStdGen -- IO StdGen
Notice the IO
scarlet letter; there is communication with world.
run :: RandState a -> IO a
run sa = do
g <- getStdGen
return $ evalState sa g
> run threeInts
> run threeInts
> run threeInts
Wait, still the same each time... The random StdGen
is chosen when the process starts. And there is no "connection" between the calls to threeInt
s.
There's a way to "mutate" the "global" random number generator.
> :t newStdGen -- IO StdGen
Replace the following:
run' sa = do
g <- newStdGen
return $ evalState sa g
> run' threeInts
> run' threeInts
> run' threeInts
Now we're in business!