1
votes

I'm learning about state monads and i'm a little confused.

I have a data type

data GameState = GameState (Map String Double) Double Bool
deriving (Eq, Show)

The second argument Double is a direction

and of course the state monad definition

newtype State s a = StateOf (s -> (s, a))
deState (StateOf stf) = stf
get = StateOf (\s0 -> (s0, s0))
put s = StateOf (\s0 -> (s , ()))
modify f = get >>= \s -> put (f s)

So how do I write a function to get the direction

getDirection:: State GameState Double

I've tried

getDirection = do
     x <- get
     return x

But this will just return GameState, how do I get what the direction currently is?

And when I want to change the direction do I use put or modify?

1

1 Answers

5
votes

Let’s start with what you have so far:

getDirection :: State GameState Double
getDirection = do
     x <- get
     return x

(As an aside, this is really just the same as getDirection = get, since you’re simply running get and returning its return value.)

Firstly, what is the type of x here? Your state is of type GameState, and get just gets the state, so x :: GameState. So we can pattern match on it to get:

getDirection :: State GameState Double
getDirection = do
     (GameState map dir bool) <- get
     return (GameState map dir bool)

At this point it should be obvious what to do: simply return dir instead of (GameState map dir bool).

And when I want to change the direction do I use put or modify?

You shouldn’t really ask two questions in the same post, but to answer this, let’s have a look at their types:

put    :: s        -> State s ()
modify :: (s -> s) -> State s ()

The idea is that put simply writes a new state, whereas modify takes the existing state and modifies it using the given function. These functions are in fact equivalent in power, meaning you can replace either function with the other:

-- write ‘put’ using ‘modify’
put s = modify (\_oldState -> s)

-- write ‘modify’ using ‘put’ (and ‘get’)
modify f = do
    oldState <- get
    put $ f oldState

However, usually it is easier to use put or modify in different situations. For instance, if you want to write a completely new state without reference to the old state, use put; if you want to take the existing state and change it a bit, use modify. In your case, you want to change the direction only, so it’s easiest to use modify so you can change the state with reference to what it used to be:

changeDirTo :: Double -> State GameState ()
changeDirTo newDir = modify (\(GameState map _ bool) -> GameState map newDir bool)

-- you can also do it using ‘put’, but it’s a bit harder and less elegant:
changeDirTo2 :: Direction -> State GameState ()
changeDirTo2 newDir = do
    (GameState map _ bool) <- get
    put $ GameState map newDir bool

On the other hand, if you (say) wanted to assign a completely new GameState, put would be easier:

putNewGameState :: GameState -> State GameState ()
putNewGameState gs = put gs

-- the above is the same as:
-- putNewGameState = put

-- you can also do it using ‘modify’, but it’s a bit harder and less elegant:
putNewGameState2 :: GameState -> State GameState ()
putNewGameState2 gs = put (\_oldState -> gs)