Стандартный способ объединения состояний в scalaz


Подумайте, у вас есть 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)
    }))
  }
Я уверен, что изобретаю велосипед, и такая вещь уже существует, вероятно, в более общем виде. Но, глядя сквозь скалаз, я не смог найти или узнать его. Буду признателен за любую помощь по этой теме.
Второй вопрос, описывает, как я пришел к такой проблеме. Я хотел сделать небольшую симуляцию, когда у вас есть враг (считайте, что это просто двойник), и все возможные заклинания, которые могут поразить его 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)}
}   
Потому что в траверсе Нели не присоединяются друг к другу, а отбрасываются. Вот почему я пришел к такому решению:
  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.

Каковы были бы другие функциональные подходы для проведения такого моделирования (по крайней мере, в терминах концепций). Я могу написать его просто функционально, без использования концепций высокого уровня, но цель этого упражнения заключалась именно в том, чтобы изучить продвинутые вещи. (Может быть, это именно та работа для писателя монады? или Комонад?)

Также, если stackoverflow не является лучшим местом для таких вопросы, пожалуйста, укажите, где такие вопросы должны быть заданы?

1 4

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))

Но мы этого не делаем, поэтому нам нужно что-то вроде следующего (n.b. я не гарантировал, что это делает что-то разумное с точки зрения закона):

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)
            })
        }
    }
}