Примеры монады штата Scalaз

Я не видел много примеров монады штата скалаз. Существует этот пример, но это трудно понять, и есть только один другой вопрос при переполнении стека кажется.

Я собираюсь опубликовать несколько примеров, с которыми я играл, но я бы приветствовал дополнительные. Кроме того, если кто-то может предоставить пример, почему init, modify, put и gets используются для этого, было бы здорово.

Изменить: здесь - это замечательная 2-часовая презентация о государственной монаде.

Ответ 1

Я предполагаю, что scalaz 7.0.x и следующий импорт (посмотрите историю ответов для scalaz 6.x):

import scalaz._
import Scalaz._

Тип состояния определяется как State[S, A], где S - тип состояния, а A - тип декодируемого значения. Основной синтаксис для создания значения состояния использует функцию State[S, A]:

// Create a state computation incrementing the state and returning the "str" value
val s = State[Int, String](i => (i + 1, "str")) 

Запуск вычисления состояния по начальному значению:

// start with state of 1, pass it to s
s.eval(1)
// returns result value "str"

// same but only retrieve the state
s.exec(1)
// 2

// get both state and value
s(1) // or s.run(1)
// (2, "str")

Состояние может быть передано через вызовы функций. Чтобы сделать это вместо Function[A, B], определите Function[A, State[S, B]]]. Используйте функцию State...

import java.util.Random
def dice() = State[Random, Int](r => (r, r.nextInt(6) + 1))

Затем синтаксис for/yield может использоваться для создания функций:

def TwoDice() = for {
  r1 <- dice()
  r2 <- dice()
} yield (r1, r2)

// start with a known seed 
TwoDice().eval(new Random(1L))
// resulting value is (Int, Int) = (4,5)

Вот еще один пример. Заполните список вычислениями TwoDice().

val list = List.fill(10)(TwoDice())
// List[scalaz.IndexedStateT[scalaz.Id.Id,Random,Random,(Int, Int)]]

Используйте последовательность, чтобы получить State[Random, List[(Int,Int)]]. Мы можем предоставить псевдоним типа.

type StateRandom[x] = State[Random,x]
val list2 = list.sequence[StateRandom, (Int,Int)]
// list2: StateRandom[List[(Int, Int)]] = ...
// run this computation starting with state new Random(1L)
val tenDoubleThrows2 = list2.eval(new Random(1L))
// tenDoubleThrows2  : scalaz.Id.Id[List[(Int, Int)]] =
//   List((4,5), (2,4), (3,5), (3,5), (5,5), (2,2), (2,4), (1,5), (3,1), (1,6))

Или мы можем использовать sequenceU, который выведет типы:

val list3 = list.sequenceU
val tenDoubleThrows3 = list3.eval(new Random(1L))
// tenDoubleThrows3  : scalaz.Id.Id[List[(Int, Int)]] = 
//   List((4,5), (2,4), (3,5), (3,5), (5,5), (2,2), (2,4), (1,5), (3,1), (1,6))

Другой пример с State[Map[Int, Int], Int] для вычисления частоты сумм в списке выше. freqSum вычисляет сумму частот бросков и отсчетов.

def freqSum(dice: (Int, Int)) = State[Map[Int,Int], Int]{ freq =>
  val s = dice._1 + dice._2
  val tuple = s -> (freq.getOrElse(s, 0) + 1)
  (freq + tuple, s)
}

Теперь используйте траверсу, чтобы применить freqSum поверх tenDoubleThrows. traverse эквивалентно map(freqSum).sequence.

type StateFreq[x] = State[Map[Int,Int],x]
// only get the state
tenDoubleThrows2.copoint.traverse[StateFreq, Int](freqSum).exec(Map[Int,Int]())
// Map(10 -> 1, 6 -> 3, 9 -> 1, 7 -> 1, 8 -> 2, 4 -> 2) : scalaz.Id.Id[Map[Int,Int]]

Или более лаконично, используя traverseU для вывода типов:

tenDoubleThrows2.copoint.traverseU(freqSum).exec(Map[Int,Int]())
// Map(10 -> 1, 6 -> 3, 9 -> 1, 7 -> 1, 8 -> 2, 4 -> 2) : scalaz.Id.Id[Map[Int,Int]]

Обратите внимание, что поскольку State[S, A] является псевдонимом типа для StateT[Id, S, A], tenDoubleThrows2 заканчивается набираемым как Id. Я использую copoint, чтобы вернуть его обратно в тип List.

Короче говоря, кажется, что ключом к использованию состояния является наличие функций, возвращающих функцию, изменяющую состояние и желаемое фактическое значение результата... Отказ от ответственности: я никогда не использовал State в производственном коде, просто пытаясь получить почувствуйте это.

Дополнительная информация о комментарии @ziggystar

Я отказался от использования stateT, может быть, кто-то еще может показать, может ли быть добавлено StateFreq или StateRandom для выполнения комбинированного вычисления. Вместо этого я нашел, что состав двух трансформаторов состояния можно объединить следующим образом:

def stateBicompose[S, T, A, B](
      f: State[S, A],
      g: (A) => State[T, B]) = State[(S,T), B]{ case (s, t) =>
  val (newS, a) = f(s)
  val (newT, b) = g(a) apply t
  (newS, newT) -> b
}

Предполагалось, что g является одной функцией параметра, принимающей результат первого трансформатора состояния и возвращающего трансформатор состояния. Тогда будет работать следующее:

def diceAndFreqSum = stateBicompose(TwoDice, freqSum)
type St2[x] = State[(Random, Map[Int,Int]), x]
List.fill(10)(diceAndFreqSum).sequence[St2, Int].exec((new Random(1L), Map[Int,Int]()))

Ответ 2

Я наткнулся на интересный блог-блог Grok Haskell Monad Transformers от sigfp, который имеет пример применения двух государственных монадов через монадный трансформатор. Вот скаляз.

В первом примере показана монада State[Int, _]:

val test1 = for {
  a <- init[Int] 
  _ <- modify[Int](_ + 1)
  b <- init[Int]
} yield (a, b)

val go1 = test1 ! 0
// (Int, Int) = (0,1)

Итак, у меня есть пример использования init и modify. Немного поиграв с этим, init[S] оказывается действительно удобным для генерации значения State[S,S], но другое, что он позволяет, - это доступ к состоянию внутри понимания. modify[S] - это удобный способ преобразования состояния внутри понимания. Таким образом, приведенный выше пример можно прочитать как:

  • a <- init[Int]: начните с состояния Int, установите его как значение, обернутое монадой State[Int, _] и привяжите ее к a
  • _ <- modify[Int](_ + 1): увеличивать состояние Int
  • b <- init[Int]: возьмите состояние Int и привяжите его к b (так же, как и для a, но теперь состояние увеличивается)
  • введите значение State[Int, (Int, Int)], используя a и b.

Синтаксис для понимания уже делает тривиальным работать на стороне a в State[S, A]. init, modify, put и gets предоставляют некоторые инструменты для работы со стороной S в State[S, A].

второй пример в сообщении в блоге переводится на:

val test2 = for {
  a <- init[String]
  _ <- modify[String](_ + "1")
  b <- init[String]
} yield (a, b)

val go2 = test2 ! "0"
// (String, String) = ("0","01")

Очень то же объяснение, что и test1.

третий пример более сложный, и я надеюсь, что есть что-то более простое, что я еще не обнаружил.

type StateString[x] = State[String, x]

val test3 = {
  val stTrans = stateT[StateString, Int, String]{ i => 
    for {
      _ <- init[String]
      _ <- modify[String](_ + "1")
      s <- init[String]
    } yield (i+1, s)
  }
  val initT = stateT[StateString, Int, Int]{ s => (s,s).pure[StateString] }
  for {
    b <- stTrans
    a <- initT
  } yield (a, b)
}

val go3 = test3 ! 0 ! "0"
// (Int, String) = (1,"01")

В этом коде stTrans выполняет преобразование обоих состояний (приращение и суффикс с "1"), а также вытягивает состояние String. stateT позволяет нам добавить преобразование состояния на произвольную монаду M. В этом случае это состояние Int, которое увеличивается. Если мы позвоним stTrans ! 0, мы получим M[String]. В нашем примере M есть StateString, поэтому мы получим StateString[String], который равен State[String, String].

Трудная часть здесь заключается в том, что мы хотим вытащить значение состояния Int из stTrans. Для этого используется initT. Он просто создает объект, который предоставляет доступ к состоянию так, как мы можем flatMap с stTrans.

Edit: Оказывается, вся эта неловкость может быть устранена, если мы действительно повторно используем test1 и test2, которые удобно хранят запрашиваемые состояния в элементе _2 их возвращенных кортежей:

// same as test3:
val test31 = stateT[StateString, Int, (Int, String)]{ i => 
  val (_, a) = test1 ! i
  for (t <- test2) yield (a, (a, t._2))
}

Ответ 3

Вот небольшой пример того, как можно использовать State:

Определите небольшую "игру", где некоторые игровые единицы сражаются с боссом (кто также является игровой единицей).

case class GameUnit(health: Int)
case class Game(score: Int, boss: GameUnit, party: List[GameUnit])


object Game {
  val init = Game(0, GameUnit(100), List(GameUnit(20), GameUnit(10)))
}

Когда игра продолжается, мы хотим отслеживать состояние игры, поэтому давайте определим наши "действия" в терминах государственной монады:

Пусть удар босса сильно, поэтому он теряет 10 из своего health:

def strike : State[Game, Unit] = modify[Game] { s =>
  s.copy(
    boss = s.boss.copy(health = s.boss.health - 10)
  )
}

И босс может нанести ответный удар! Когда он делает все в партии, теряет 5 health.

def fireBreath : State[Game, Unit] = modify[Game] { s =>
  val us = s.party
    .map(u => u.copy(health = u.health - 5))
    .filter(_.health > 0)

  s.copy(party = us)
}

Теперь мы можем записать эти действия в play:

def play = for {
  _ <- strike
  _ <- fireBreath
  _ <- fireBreath
  _ <- strike
} yield ()

Конечно, в реальной жизни игра будет более динамичной, но для моего небольшого примера достаточно еды:)

Мы можем запустить его сейчас, чтобы увидеть конечное состояние игры:

val res = play.exec(Game.init)
println(res)

>> Game(0,GameUnit(80),List(GameUnit(10)))

Итак, мы едва ударили по боссу, и один из подразделений умер, RIP.

Пункт здесь - это композиция. State (который является просто функцией S => (A, S)) позволяет вам определять действия, которые приводят к результатам, а также манипулировать каким-либо состоянием, не зная слишком много, откуда приходит государство. Часть Monad дает вам композицию, поэтому ваши действия могут быть составлены:

 A => State[S, B] 
 B => State[S, C]
------------------
 A => State[S, C]

и т.д.

P.S. Что касается различий между get, put и modify:

modify можно рассматривать как get и put вместе:

def modify[S](f: S => S) : State[S, Unit] = for {
  s <- get
  _ <- put(f(s))
} yield ()

или просто

def modify[S](f: S => S) : State[S, Unit] = get[S].flatMap(s => put(f(s)))

Поэтому, когда вы используете modify, вы концептуально используете get и put, или можете просто использовать их самостоятельно.