Propagation of State Monad

2019-03-31 08:11发布

问题:

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?

回答1:

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 two runState 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" the runState needs to be.