GreenSaguaro
GreenSaguaro

Reputation: 3407

Having trouble to understand MonadState put with return

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

Answers (3)

David Young
David Young

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

NovaDenizen
NovaDenizen

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

chi
chi

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

Related Questions