Я припускаю, що scalaz 7.0.x та такі імпорти (див. Історію відповідей для scalaz 6.x ):
import scalaz._
import Scalaz._
Тип стану визначається як State[S, A]де Sє типом стану і Aє типом значення, що декорується. Основний синтаксис для створення значення стану використовує State[S, A]функцію:
val s = State[Int, String](i => (i + 1, "str"))
Щоб запустити обчислення стану на початковому значенні:
s.eval(1)
s.exec(1)
s(1)
Стан може передаватися через виклики функцій. Для цього замість 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)
TwoDice().eval(new Random(1L))
Ось ще один приклад. Заповніть список TwoDice()обчисленнями стану.
val list = List.fill(10)(TwoDice())
Використовуйте послідовність, щоб отримати a State[Random, List[(Int,Int)]]. Ми можемо надати псевдонім типу.
type StateRandom[x] = State[Random,x]
val list2 = list.sequence[StateRandom, (Int,Int)]
val tenDoubleThrows2 = list2.eval(new Random(1L))
Або ми можемо скористатися тим, sequenceUщо зробить висновок про типи:
val list3 = list.sequenceU
val tenDoubleThrows3 = list3.eval(new Random(1L))
Ще один приклад 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]
tenDoubleThrows2.copoint.traverse[StateFreq, Int](freqSum).exec(Map[Int,Int]())
Або ще коротше, використовуючи traverseUвисновок про типи:
tenDoubleThrows2.copoint.traverseU(freqSum).exec(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]()))
Stateмонада не є "державним перетворювачем" у реальності? І як друге запитання: чи є якийсь більш приємний спосіб поєднати кістки та підсумки в одній державній монаді? Як би ви це зробили з огляду на дві монади?