State Monad with `put` Function

1.4k views Asked by At

Looking at the State Monad's wiki, I'm trying to understand the runState and put functions.

As I understand runState, it takes a first argument of State, which has a "universe", s, and a value, a. It takes a second argument of a universe. Finally, it returns a (a, s) where a is the new value and s is the new universe?

ghci> :t runState
runState :: State s a -> s -> (a, s)

Example:

ghci> let s = return "X" :: State Int String
ghci> runState s 100
("X",100)

However, I'm don't understand the put result:

ghci> runState (put 5) 1
((),5)

Since runState returns an (a, s), why is the a of type ()?

I'm not confident on my above attempted explanations. Please correct me, and answer my question on put.

3

There are 3 answers

0
Luis Casillas On

The easiest way to really understand State, IMHO, is just to study the code and understand it well enough that you can implement it from memory, like I'm about to do:

import Control.Applicative

newtype State s a = State { runState :: s -> (a, s) }

instance Functor (State s) where
    fmap f fa = State $ \s -> f (runState fa s)

instance Applicative (State s) where
    pure a = State $ \s -> (a, s)
    ff <*> fa = State $ \ s -> 
        let (f, s') = runState ff s
            (a, s'') = runState fa s'
        in (f a, s'')

instance Monad (State s) where
    return = pure
    ma >>= f = State $ \s -> 
        let (a, s') = runState ma s
            (b, s'') = runState (f a) s'
        in (b, s'')

get :: State s s
get = State $ \s -> (s, s)

put :: s -> State s ()
put s = State $ \_ -> ((), s)

modify :: (s -> s) -> State s ()
modify f = State $ \s -> ((), f s)

Since runState returns an (a, s), why is the a of type ()?

That really is just arbitrary/conventional. Taking the above as a baseline, we could just as well write modify, get and put like this:

-- | Replaces the state with the result of applying it to the supplied
-- function.  The result of the action is the original state.
modify :: (s -> s) -> State s s
modify f = State $ \s -> (s, f s)

-- `get` is just what you get when you `modify` with an identity function.
get :: State s s
get = modify (\s -> s)

-- This version of `put` differs from the canonical one because it returns 
-- the old value.
put :: s -> State s s
put s = modify (\_ -> s) 

In this version modify and put have the same effects as the original, but additional produce the old state as their result. Clients that use modify and put only for the effect would not generally notice the difference.

Alternatively, the "return old state" versions of modify and put can be written in terms of the official ones. For example:

swap :: s -> State s s
swap s = do
    orig <- get
    put s
    return orig

So most of these operations are interdefinable, it doesn't much matter which ones are "basic" and which ones not...

0
AudioBubble On

When using put with the State monad, it has the type s -> State s ().

put sets the state to its argument, and that's all it does. As for its return value: it's essentially a dummy value, because there's nothing useful to return.

This is also evident in its definition put s = state $ \ _ -> ((), s).

0
John F. Miller On

The runState function takes an action in the State monad and an initial state and gives you back both the result of the calculation and the final state.

A typical use case for runState is to hand it a complex action with an intial and to then get the final result and the state back. In your example, the action is a simple primitive, put.

The put action takes a new state and produces () (pronounced unit) for a value. It is very similar to how putStrLn has the type IO () It performs an action in the monad, but does not produce a useful value.

So, with runState (put 5) 1 the initial state, 1 gets blown away by the new state 5. The result the state calculation is the result of put that is ().

Just for kicks, lets look at something just a little more interesting:

runState (puts 5 >> return "hello world!") undefined
  --> ("hello world!", 5)

Here we have two actions glued together with >> (I read this as "then", it is a limited form of bind >>= where we just drop the result from the left hand side). The first action changes the state to 5 the second doesn't touch the state, but results in the string "hello world!" which then becomes the value of the entire calculation.