【问题标题】:Standard way to combine States in scalaz在scalaz中组合状态的标准方法
【发布时间】:2026-01-14 20:00:01
【问题描述】:

考虑一下,你有 Nel 状态(Nel 代表 NonEmptyList,使事情更短), 并且您想将状态组合为一个状态,使用一些函数 f,用于状态的左侧部分和 g 表示状态的正确部分。

所以你想要这样的东西:

def foldStatesG[S, A](in: NonEmptyList[State[S, A]])(f: (A, A) => A)(g: (S, S) => S): State[S, A] = {
    in.foldLeft1((s1, s2) => State(e => {
      val ss1 = s1(e)
      val ss2 = s2(e)
      g(ss1._1, ss2._1) -> f(ss1._2, ss2._2)
    }))
  }

我敢肯定,我正在发明自行车,而且这种东西已经存在,可能以更一般的方式存在。但是,通过 scalaz 我没有找到或认出它。如果您对此主题有任何帮助,将不胜感激。


第二个问题,描述了我是如何遇到这样的问题的。我想做一个小模拟,当你有一个敌人(考虑它只是一个双),以及所有可能击中他的法术Nel[Spell]。所以基本上我想生成所有可能的序列。例如,如果 Nel[Spell] = ($, #),那么给定一个敌人 E,进程看起来像

E -> (), then Nel(E -> $, E -> #), then Nel(E -> $$, E -> ##, E -> $#, E-> #$) etc.. (pseudo code)

无需过多介绍,我需要这样的东西:

def combineS(variations: NonEmptyList[Spell]): State[NonEmptyList[(Enemy, List[Spell])], Boolean]

换句话说,你为它提供了所有可能的动作,它模拟了所有可能的状态。您可以将其视为一种决策树,每一步都有分支。 因此,我定义了如何继续使用一个咒语。

def combineS1(spell: Spell): State[(NonEmptyList[(Enemy, List[Spell])]), Boolean]
    // some logic

问题是,我无法通过简单的遍历来实现它:

def combineS(variations: NonEmptyList[Spell]): State[NonEmptyList[(Enemy, List[Spell])], Boolean] = {
    val x = variations traverse combine1 // State[Nel[..], Nel[Boolean]
    x map { _.suml1(conjunction)}
}   

因为在遍历中,Nels 不是相互附加的,而是被丢弃的。 这就是我想出这个解决方案的原因:

  def combineS(variations: NonEmptyList[Spell]): State[NonEmptyList[(Enemy, List[Spell])], AllDead] = {
    val x: NonEmptyList[State[NonEmptyList[(Enemy, List[Spell])], Boolean]] = variations map combineS
    foldStates(x)(_ && _)(append)
  } 

该代码实际上是有效的,但我觉得有一种方法可以遍历它,而无需额外的 foldState

进行此类模拟的其他功能方法是什么(至少在概念方面)。我可以简单地编写它,而不使用高级概念,但这个练习的目的正是为了学习高级的东西。 (也许这正是作家 Monad 的工作?还是 Comonad?)

另外,如果 * 不是解决此类问题的最佳场所,请指出,此类问题应该在哪里提出?

【问题讨论】:

  • 我认为这是 Stack Overflow 的一个好问题——只是还没有机会回答。

标签: scala functional-programming state scalaz


【解决方案1】:

好吧,回答第一部分,如果我们在另一个宇宙中,Scalaz 已经有一个 Biapplicative 类型类,我们可以按如下方式使用它:

def foldStatesG[S, A](states: NonEmptyList[State[S, A]], s: (S, S) ⇒ S, a: (A, A) ⇒ A): State[S, A] =
  states.foldLeft1(IndexedStateT.indexedStateTBiapplicative[Id, S].bilift2(s, a))

但我们不需要,所以我们需要类似以下的东西(注意,我并没有确保这在法律上是明智的):

trait Biapplicative[F[_, _]] extends Bifunctor[F] {
  def bipure[A, B](a: ⇒ A, b: ⇒ B): F[A, B]
  def biapply[A, B, C, D](fab: ⇒ F[A, B])(f: ⇒ F[A ⇒ C, B ⇒ D]): F[C, D]
  def bilift2[A, B, C, D, E, G](fab: (A, B) ⇒ C, fde: (D, E) ⇒ G): (F[A, D], F[B, E]) ⇒ F[C, G] =
    (fad: F[A, D], fbe: F[B, E]) ⇒
      biapply(fbe)(bimap[A, D, B ⇒ C, E ⇒ G](fad)(fab.curried, fde.curried))
}

object Biapplicative {
  implicit def indexedStateTBiapplicative[F[_], S1](implicit F: Applicative[F]) =
    new Biapplicative[IndexedStateT[F, S1, ?, ?]] {
      def bipure[A, B](a: ⇒ A, b: ⇒ B) =
        StateT.constantIndexedStateT(b)(a)
      def biapply[A, B, C, D]
          (fab: ⇒ IndexedStateT[F, S1, A, B])
          (f: ⇒ IndexedStateT[F, S1, A ⇒ C, B ⇒ D]) =
        new IndexedStateT[F, S1, C, D] {
          def apply(initial: S1): F[(C, D)] =
            F.ap(fab(initial))(F.map(f(initial)) {
              case (a2c, b2d) ⇒ Bifunctor[Tuple2].bimap(_)(a2c, b2d)
            })
        }
    }
}

【讨论】:

  • 非常感谢您的评论!