【问题标题】:Haskell - How would I run a list of state monads?Haskell - 我将如何运行状态单子列表?
【发布时间】: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,但我的大脑正被这些类型所困扰。

什么是好的实现?

【问题讨论】:

  • 什么是开始状态?当列表为空时会发生什么?你的 CreatureMonoid 吗?
  • 我看到的问题是您提出的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


【解决方案1】:

State Creature Creature 是这种计算的错误类型。如您所见,您可以摆脱它,但它使事情变得不必要地复杂化,因为您实际上根本不关心状态变量!您只需使用它来存储函数的原始输入……然后将其丢弃在 applyChanges 中。

一个更容易使用的类型是Creature -> Creature,然后如果你有一个这样的函数列表来应用你只需要将它们全部组合起来,你可以通过折叠来完成:

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

【讨论】:

  • 我是 Haskell 的新手,很难想象这在实践中是如何工作的。如果我们有一个将 2 添加到 stat 的 StatModifer,以及一个将此修饰符应用于所有 stat 的函数,那么实现会是什么样子?
  • @DanAmbrogio:如果我理解您的问题,您可以使用addMod :: StatModifier -> Stat a -> Stat a; addMod m (Stat ms a) = Stat (m : ms) a 为统计数据添加修饰符,并使用modAll :: StatModifier -> Creature -> Creature; modAll m c = c { strength = addMod m (strength c), … } 之类的助手来修改所有统计数据。然后,您将创建 Creature -> Creature 函数,如 enhanceAll = modAll (StatModifier Enhancement 2) 并组合它们或将它们用作 State 操作。 foldr (.) id [f, g, h] xf (g (h (id x))) 相同:它从右到左应用每个函数。
  • 可能值得自己提出这样的问题。在答案中肯定会更具可读性。
猜你喜欢
  • 1970-01-01
  • 2018-07-04
  • 1970-01-01
  • 1970-01-01
  • 2014-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多