4

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?

Nick Acosta
  • 1,890
  • 17
  • 19
  • 2
    What is the start state? What should happen when the list is empty? Is your `Creature` a `Monoid`? – Bergi May 26 '17 at 05:04
  • I've edited my question and added an example of a beginning and end state along with some more types for clarification. This is a simplified version of my creature and the real thing is not a monoid. If the list is empty I guess I would expect back the original creature. – Nick Acosta May 26 '17 at 05:28
  • 1
    The problem I saw is that your proposed `applyChanges` function does not accept a creature in its original state – Bergi May 26 '17 at 05:43
  • Edited. I'm open to any changes to the above. My overall goal is to get a creature through each of the state actions in that list. – Nick Acosta May 26 '17 at 05:46
  • 6
    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 (.) id` – amalloy May 26 '17 at 06:40
  • Ahh, I believe you're right. I jumped the gun with unneeded complexity here, and probably misused the state monad as well. If you want, add an answer and I'll mark it as such. – Nick Acosta May 26 '17 at 14:40
  • 1
    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 May 26 '17 at 18:33
  • 1
    @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 May 26 '17 at 18:37
  • 1
    @amalloy, yes, `State Creature ()` is probably the right place to start. – dfeuer May 26 '17 at 18:41
  • @defeuer I've thought about that too and I'm keeping it in the back of my mind. It's something I want to do eventually, but this works for now. When that time comes I think converting from a function to State will be an easy change, except for the fold part, which im still a bit hazy on. – Nick Acosta May 26 '17 at 19:08
  • 1
    @NickAcosta, the folds to look at here (in `Data.Foldable`) are `traverse_` and (less generally) `sequenceA_`. You well soon find yourself interested in the traversals `traverse` and `sequenceA`. – dfeuer May 26 '17 at 20:32
  • Thanks I will check those out. – Nick Acosta May 26 '17 at 21:08

1 Answers1

3

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
amalloy
  • 89,153
  • 8
  • 140
  • 205
  • 1
    I'm new to haskell and having difficulty visualizing how this would work in practice. If we had a StatModifer that added 2 to a stat, and a function that applied this modifier to all stats, what would the implementation look like? – Dan Ambrogio May 26 '17 at 17:51
  • 2
    @DanAmbrogio: If I understand your question, you would have `addMod :: StatModifier -> Stat a -> Stat a; addMod m (Stat ms a) = Stat (m : ms) a` to add a modifier to a stat, and helpers like `modAll :: StatModifier -> Creature -> Creature; modAll m c = c { strength = addMod m (strength c), … }`, for example, to modify all stats. You would then make `Creature -> Creature` functions like `enhanceAll = modAll (StatModifier Enhancement 2)` and compose them or use them as `State` actions. `foldr (.) id [f, g, h] x` is the same as `f (g (h (id x)))`: it applies each function from right to left. – Jon Purdy May 27 '17 at 03:24
  • Might be worth it's own SO question. It would certainly be more readable in an answer. – Nick Acosta May 27 '17 at 19:15