1

Background: I'm creating a game with a stateful monad for reading and writing changes to the global state of the game.

I would like to divide my game into components, such as "Characters", providing a domain specific way for these components to interact with the global state. Ideally this might be define specific actions of the form MonadState Character m => m a that I could use, but each such m a would enact changes on the parent (global state).

I've searched around for converting between state monads, or providing an interface from one state monad to another, but the specific language for this is beyond my scope of knowledge. I am also already using Lenses, and I'm wondering if I can do something with them.

EDIT:

I'd like to be able to do something like moveCharacter :: MonadState Character m => Int -> Int -> m ()

and have that perform move :: MonadState World m => Int -> Int -> m () inside. Basically, abstracting the world specifics away from the character.

Thanks!

2 Answers2

3

Sounds like you're looking for Zoom, which lets you convert a stateful action on the view of a lens into a stateful action on the source of the lens.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • I looked into using Zoom, but I'm not sure if it fits my added use case above. I'm not sure how to set that up so that I can construct the `MonadState Character` monad, but be linked to a monadic action on the world. – g. nicholas d'andrea Oct 26 '15 at 02:09
  • Well... you can't do exactly what you're asking. Any state you want to be able to access has to at least be available according to the type signature -- you can't lie about what state you need access to! If your action needs access to the world state, well, by golly, the type will have to say that it can access the world state. – Daniel Wagner Oct 26 '15 at 03:35
  • Hrm. That makes sense. What I am trying to achieve is preventing character-specific code from being able to make unauthorized changes on the parent state. My other approach could be to make some data type where a parent state change is a data constructor (like `data STDiff = Move Int Int | ...`) – g. nicholas d'andrea Oct 26 '15 at 04:04
0

You actually need 2 conversion functions: one to extract the character from the world and another one to modify the world with the modified character. You can put them together like this:

extractCharacter :: World -> Character
extractCharacter = error "Tried to call placeholder function"

replaceCharacter :: World -> Character -> World
replaceCharacter = error "Tried to call placeholder function"

runCharacterSubroutine :: (Functor m) =>
  StateT Character m a -> StateT World m a
runCharacterSubroutine act = StateT $ \w ->
  fmap (\(a,c') -> (a,replaceCharacter w c')) $
  runStateT act (extractCharacter w)

In your game, you probably want something a little more complicated, but that's just a matter of adding an extra parameter to extractCharacter and replaceCharacter

Note that the function I gave only works if the StateT is at the top of the monad transformer stack. If it's not: you will have to use the mmorph package

Jeremy List
  • 1,756
  • 9
  • 16