4
votes

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?

1
What is the start state? What should happen when the list is empty? Is your Creature a Monoid?Bergi
The problem I saw is that your proposed applyChanges function does not accept a creature in its original stateBergi
Why are these in State at all, if the state is required to always be "the original input"? It seems each change should just be Creature -> Creature, and then your function applyChanges :: [Creature -> Creature] -> Creature -> Creature is very easy to implement: applyChanges = foldr (.) idamalloy
Using State may be premature in some sense, but I'd do it anyway, because I can immediately think of reasons you'd want to. 1. If you want to get not only the final state but a list of intermediate states as well. You could do this with scanl, but if you start with State it's really natural. 2. You want to add some other effects. If you start with State, you need only switch to StateT on the relevant underlying action type.dfeuer
@dfeuer In that case you'd want State Creature a, where the state value is what you actually want as a result (via execState), and not the monadic value you'd get from evalState, right? That certainly makes some sense to me, but State Creature Creature seems too specific, requiring each transformation to end with get to move the Creature state into the monadic value. State Creature () looks more reasonable, as a target for composing a list of stateful transformations.amalloy

1 Answers

4
votes

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