Search code examples
haskellfunctional-programmingstate-monad

Haskell - How would I run a list of state monads?


I'm somewhat new to Haskell and I'm having some trouble with the State monad.

I have created the following types. Stat a has a monoid, functor, applicative and monad instance created for it.

The "main" type in my program is creature and it has many arguments:

data Creature = Creature {
    strength  :: Stat Integer,
    dexterity :: Stat Integer,
    ...
}

data Stat a = Stat {
    modifiers :: [StatModifier],
    stat      :: a
}

data StatModifier = StatModifier {
    modifierType :: ModifierType,
    value        :: Integer
}

data ModifierType = 
  Enhancement
  | Morale
  | ...

There are many things that can happen to a creature. I chose to represent those things with the state monad:

anyPossibleChange :: State Creature Creature

This could be damage being done to the creature, an increase to the creature's strength, basically anything. The possibility of anything made me think the State monad was a good choice here. I will accept a creature in it's original state, perform some modification, and return the original state and the new state in a tuple.

An original state might be:

Creature {
    strength = Stat [] 10,
    dexterity = Stat [] 10
}

An end state might be:

Creature {
    strength = Stat [StatModifier Enhancement 2] 10,
    dexterity = Stat [StatModifier Enhancement 4, StatModifier Morale 2] 10
}

I would like to build a list of all the changes a creature needs to go through and then run the creature through all of those changes.

This is the signature I had in mind, but I am having trouble coming up with an implementation. I am open to it being different.

applyChanges :: Creature -> [State Creature Creature] ->  Creature

I feel like I should be able to do this with a fold, possibly FoldM but my brain is getting hung up around the types.

What would a good implementation be?


Solution

  • State Creature Creature is the wrong type for this kind of computation. As you saw, you can get away with it, but it complicates things unnecessarily, because you don't actually care about the state variable at all! You simply use it to store the function's original input...but then throw it away in applyChanges.

    A type that would be much easier to work with is Creature -> Creature, and then if you have a list of such functions to apply you simply want to compose them all, which you can do with a fold:

    applyChanges :: [Creature -> Creature] -> Creature -> Creature
    applyChanges = foldr (.) id