Примеры монады штата Скалаз

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

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

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

    Я предполагаю, что 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]]] . Используйте функцию состояния …

     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() вычислений состояния 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]())) 

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

    Первый пример показывает State[Int, _] monad:

     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, _] и привяжите его к
    • _ <- modify[Int](_ + 1) : увеличить значение Int
    • b <- init[Int] : взять состояние Int и привязать его к b (то же, что и для a но теперь состояние увеличивается)
    • выведите значение State[Int, (Int, Int)] используя a и b .

    Синтаксис для понимания уже делает тривиальным работать на стороне A в State[S, A] . init , modify , put и получать некоторые инструменты для работы на стороне 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 stTrans ! 0 мы получим M[String] . В нашем примере M является StateString , поэтому мы закончим с StateString[String] который является State[String, String] .

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

    Edit: Оказывается, вся эта неловкость может быть устранена, если мы действительно повторно использовали test1 и test2 которые удобно 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)) } 

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

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

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

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

     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] 

    и так далее.

    PS Что касается различий между 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 , или вы можете просто использовать их в одиночку.

    Interesting Posts

    Комбинация смешанного режима построена против версии «v2.0.50727» среды выполнения

    Разница между -pthread и -lpthread при компиляции

    Как добавить подпись по умолчанию в Outlook

    Построение нескольких временных рядов на одном и том же участке с использованием ggplot ()

    Почему системы x86-64 имеют только 48-битное виртуальное адресное пространство?

    Объявить постоянный массив

    Должен ли я использовать \ d или для сопоставления цифр в регулярном выражении Perl?

    Поиск первой пустой строки, затем запись на нее

    Бесплатный инструмент для проверки исходного кода C / C ++ на основе набора стандартов кодирования?

    Как связать raw html в Angular2

    пользовательские сообщения об ошибках с формой модели

    Миграция Google Диска API v3

    Использовать пакетный рейк или просто грабли?

    Invoke zsh, затем * source * другой файл (при входе в интерактивный режим)

    Где javafx.scene.image.Image (“flower.png”) искать flower.png?

    Давайте будем гением компьютера.