Stateful Functions (continued)
First, we'll talk about the definition of StatefulFunction
in the library. Then we'll work through a second example with
StatefulFunction
s.
State Library
The State
library in Control.Monad.State
provides the
functionality we implemented in StatefulFunction
. 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 StatefulFunction
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,
so need to use the lowercase state
function provided by the library
to build 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 ()
Example: Random Numbers
> 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 RandState monad
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!