【发布时间】:2017-05-26 03:58:34
【问题描述】:
我对 Haskell 有点陌生,我在使用 State monad 时遇到了一些麻烦。
我创建了以下类型。
Stat a 有一个为它创建的 monoid、functor、applicative 和 monad 实例。
我的程序中的“主要”类型是生物,它有很多参数:
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
| ...
有很多事情可能发生在一个生物身上。我选择用 state monad 来表示这些东西:
anyPossibleChange :: State Creature Creature
这可能是对生物造成的伤害,增加了生物的力量,基本上是任何事情。任何事情的可能性让我认为 State monad 是一个不错的选择。我将接受一个处于原始状态的生物,进行一些修改,然后在一个元组中返回原始状态和新状态。
原始状态可能是:
Creature {
strength = Stat [] 10,
dexterity = Stat [] 10
}
结束状态可能是:
Creature {
strength = Stat [StatModifier Enhancement 2] 10,
dexterity = Stat [StatModifier Enhancement 4, StatModifier Morale 2] 10
}
我想建立一个生物需要经历的所有变化的列表,然后让生物通过所有这些变化。
这是我想到的签名,但我在想出一个实现时遇到了麻烦。我愿意接受它与众不同。
applyChanges :: Creature -> [State Creature Creature] -> Creature
我觉得我应该可以通过折叠来做到这一点,可能是FoldM,但我的大脑正被这些类型所困扰。
什么是好的实现?
【问题讨论】:
-
什么是开始状态?当列表为空时会发生什么?你的
Creature是Monoid吗? -
我看到的问题是您提出的
applyChanges函数不接受原始状态的生物 -
如果要求状态始终是“原始输入”,为什么这些都在状态中?看来每次改动都应该只是
Creature -> Creature,然后你的函数applyChanges :: [Creature -> Creature] -> Creature -> Creature就很容易实现了:applyChanges = foldr (.) id -
使用
State在某种意义上可能为时过早,但我还是会这样做,因为我可以立即想到您想要使用的原因。 1. 如果您不仅想获得最终状态,还想获得中间状态列表。你可以用scanl来做这件事,但是如果你从State开始,那真的很自然。 2.您想添加一些其他效果。如果您以State开头,则只需在相关的底层操作类型上切换到StateT。 -
@dfeuer 在这种情况下,您需要
State Creature a,其中状态值是您实际想要的结果(通过 execState),而不是您从 evalState 获得的一元值,正确的?这对我来说当然是有道理的,但State Creature Creature似乎太具体了,要求每次转换都以get结束,以将Creature状态移动到单子值。State Creature ()看起来更合理,作为组成有状态转换列表的目标。
标签: haskell functional-programming state-monad