Reputation: 3407
In the following code, I get the expected result:
Prelude Control.Monad.State> runState ( put 10 >> return 5 ) 9
(5,10)
But what really baffles me is how precisely the value given to put
is available to return
and ends up in the final result, or how exactly the value is hidden and taken out later. I have sought out many tutorials on state in Haskell, but I have not yet found an explanation that really, really breaks it down in a way that is not high level.
My understanding of >>
is that the value on the left will be dropped, so this adds to my confusion of how the value for put
is passed along.
Thanks in advance for help in understanding this.
Upvotes: 0
Views: 90
Reputation: 10793
State s a
is ultimately just a wrapper around the function s -> (a, s)
so, for the purposes of learning, it might be easier to just consider this
function directly, without the extra wrapping. We can also define a type synonym
that could make it easier to read. I will show the type sigatures without
it first, then with it.
We have the monad definition (with renamed functions):
sReturn :: a -> (s -> (a, s))
sReturn x = \s -> (x, s)
(+>>=) :: (s -> (a, s)) -> (a -> (s -> (b, s))) -> (s -> (b, s))
f +>>= g = \s1 ->
let (a, s2) = f s1
(b, s3) = (g a) s2
in (b, s3)
(+>>) :: (s -> (a, s)) -> (s -> (b, s)) -> (s -> (b, s))
f +>> g = f +>>= (\_ -> g)
We can also define the State-specific functions:
putS :: s -> (s -> ((), s))
putS newState = \s -> ((), newState)
getS :: s -> (s, s)
getS = \s -> (s, s)
runS :: (s -> (a, s)) -> s -> (a, s)
runS f s = f s
Note, I've included some extra parentheses and some things that are technically unnecessary, but will hopefully clarify things.
Anywhere you see something like (s -> (a, s))
, you can replace it with
State s a
in the above code, using this type synonym:
type State s a = s -> (a, s)
Making that replacement, we get the signatures:
sReturn :: a -> State s a
(+>>=) :: State s a -> (a -> State s b) -> State s b
(+>>) :: State s a -> State s b -> State s b
putS :: s -> State s ()
getS :: State s s
runS :: State s a -> s -> (a, s)
Now, if we translate your example, we can follow the steps of the reduction:
runS ( putS 10 +>> sReturn 5 ) 9 -- The initial expression
runS ( putS 10 +>>= (\_ -> sReturn 5) ) 9 -- Apply (+>>)
runS ( \s1 -> -- Apply (+>>=)
let (a, s2) = (putS 10) s1
(b, s3) = ((\_ -> sReturn 5) a) s2
in (b, s3) )
9
runS ( \s1 ->
let (a, s2) = (putS 10) s1
(b, s3) = (sReturn 5) s2 -- Reduce lambda application
in (b, s3) )
9
runS ( \s1 ->
let (a, s2) = (\s -> ((), 10)) s1 -- Apply putS
(b, s3) = (sReturn 5) s2
in (b, s3) )
9
runS ( \s1 ->
let (a, s2) = ((), 10) -- Reduce lambda application
(b, s3) = (sReturn 5) s2
in (b, s3) )
9
runS ( \s1 ->
let (b, s3) = (sReturn 5) 10 -- Substitute for s2 (a is not needed)
in (b, s3) )
9
runS ( \s1 ->
let (b, s3) = (\s -> (5, s)) 10 -- Apply sReturn
in (b, s3) )
9
runS ( \s1 ->
let (b, s3) = (5, 10) -- Reduce lambda application
in (b, s3) )
9
runS ( \s1 -> (5, 10) ) 9 -- Substitute for (b, s3)
(\s1 -> (5, 10)) 9 -- Apply runS
(5, 10) -- Reduce lambda application
I highly recommend trying to do this kind of step-by-step reduction when trying to learn a new Haskell abstraction like State
. That might seem like a lot of steps to go through each time but, as you work
with the abstraction more, you develop an intuition that makes easy to figure out
without having to write out the steps.
Part of the intuition for State is that a stateful, or state-dependent, value is a function from a state value to a pair containing the state-dependent value and a new state value. This allows it to both take in a new state and change its value appropriately and give back a new modified state. The State
monad methods do the threading of these state values automatically.
Also, the real State
is just a wrapper around the function type we used above. As a result, the only difference between what we have done here and the way it is "actually" done is that we have merely omitted the wrapping and unwrapping of the function value inside State
.
Upvotes: 5
Reputation: 5325
The way to think of it is that runState (put 10 >> return 5) 9
First takes 9
and holds it in the state variable. Then it starts to perform the monadic computation.
put 10
replaces the state (which is currently 9
) with 10
. Then return 5
causes the output of the monadic computation to be 5
.
runState
takes a monadic computation and an initial state value as parameters. Then it runs the monadic computation, then it returns the result of the computation alongside the final value of the state variable.
Upvotes: 2
Reputation: 116174
The state monad models an implicit mutable state/variable. put 10
writes 10 in the variable and returns a dummy value ()
. Then ... >> return 5
discards the ()
and returns 5
.
You can spot this from its type:
put :: s -> State s ()
-- ^ -- the type of the returned value
So, the whole monadic computation put 10 >> return 5
modifies the internal variable to 10, but returns 5.
Finally, runState (...) 9
provides 9
as an initial value for the implicit variable (which will be immediately overwritten), runs the computation, and then
extracts both the variable and the returned value, building a pair with these.
Upvotes: 2