I have the following function for walking around "edges" of the "graph" of my game world. It alters the state of the world--specifically, the player's location. I need to report a message alerting the player of their change in location as well.
So I could either return a tuple of (message, newWorld), or I could use a State monad. (Right? I'm new to this stuff.)
Here's my attempt at the monad approach:
walk dir = do
world <- get
let attempt = filter (\e -> edgeDirection e == dir) $ edges
edges = (worldEdges world) M.! playerLoc
playerLoc = playerLocation $ worldPlayer world
case attempt of
[] -> return "You can't go that way."
(edge : _) -> do
put world'
return message
where world' = world { worldPlayer = player' }
player' = (worldPlayer world) { playerLocation = loc }
loc = edgeLocation edge
message = "You go " ++ (downcase $ show dir)
The thing is, now the function that calls walk
has a State monad to deal with. Do I runState
it right there, or do I have that function use State monads too, like this:
parseWalk dir =
let direction = case dir of
('e' : _) -> Just East
('n' : _) -> Just North
('s' : _) -> Just South
('w' : _) -> Just West
('u' : _) -> Just Up
('d' : _) -> Just Down
_ -> Nothing
in case direction of
Just d -> walk d
Nothing -> return "You can't go that way"
Then what about parseWalk
's caller, and so forth? Should I try to keep this State monad passed around as long as possible? Should I be seeing a lot of MonadState ...
in my type signatures?
Here's one way of thinking about it:
runState
requires an initial state as its parameter. Where is this initial state available in your code? That's how far the state monad needs to propagate.Note that
runState
encapsulates the stateful computation, so that if you have tworunState
computations side-by-side, they won't see each other's states (unless you pass the result from one to the other.) This also should give you a hint how far "up" therunState
needs to be.