Ich gehe davon aus, scalaz 7.0.x und die folgenden Importe (siehe Antwortverlauf für scalaz 6.x ):
import scalaz._
import Scalaz._
Der Zustandstyp ist definiert als State[S, A]
wo S
ist der Typ des Zustands und A
ist der Typ des Wertes, der dekoriert wird. Die grundlegende Syntax zum Erstellen eines Statuswerts verwendet die folgende State[S, A]
Funktion:
val s = State[Int, String](i => (i + 1, "str"))
So führen Sie die Statusberechnung für einen Anfangswert aus:
s.eval(1)
s.exec(1)
s(1)
Der Status kann durch Funktionsaufrufe gefädelt werden. Um dies zu tun Function[A, B]
, definieren Sie Function[A, State[S, B]]]
. Verwenden Sie die State
Funktion ...
import java.util.Random
def dice() = State[Random, Int](r => (r, r.nextInt(6) + 1))
Dann kann die for/yield
Syntax verwendet werden, um Funktionen zu erstellen:
def TwoDice() = for {
r1 <- dice()
r2 <- dice()
} yield (r1, r2)
TwoDice().eval(new Random(1L))
Hier ist ein weiteres Beispiel. Füllen Sie eine Liste mit TwoDice()
Statusberechnungen.
val list = List.fill(10)(TwoDice())
Verwenden Sie die Sequenz, um eine zu erhalten State[Random, List[(Int,Int)]]
. Wir können einen Typalias bereitstellen.
type StateRandom[x] = State[Random,x]
val list2 = list.sequence[StateRandom, (Int,Int)]
val tenDoubleThrows2 = list2.eval(new Random(1L))
Oder wir können verwenden sequenceU
, um die Typen abzuleiten:
val list3 = list.sequenceU
val tenDoubleThrows3 = list3.eval(new Random(1L))
Ein weiteres Beispiel State[Map[Int, Int], Int]
für die Berechnung der Häufigkeit von Summen in der obigen Liste. freqSum
berechnet die Summe der Würfe und zählt die Frequenzen.
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)
}
Verwenden Sie nun die Traverse, um freqSum
sie aufzutragen tenDoubleThrows
. traverse
ist äquivalent zu map(freqSum).sequence
.
type StateFreq[x] = State[Map[Int,Int],x]
tenDoubleThrows2.copoint.traverse[StateFreq, Int](freqSum).exec(Map[Int,Int]())
Oder genauer gesagt, indem Sie traverseU
die Typen ableiten:
tenDoubleThrows2.copoint.traverseU(freqSum).exec(Map[Int,Int]())
Beachten Sie, dass tenDoubleThrows2 , da State[S, A]
es sich um einen StateT[Id, S, A]
Typalias für handelt , als typisiert wird Id
. Ich copoint
verwandle es wieder in einen List
Typ.
Kurz gesagt, es scheint, dass der Schlüssel zur Verwendung von state darin besteht, dass Funktionen eine Funktion zurückgeben, die den Status und den gewünschten tatsächlichen Ergebniswert ändert ... Haftungsausschluss: Ich habe noch nie state
im Produktionscode verwendet, nur um ein Gefühl dafür zu bekommen.
Zusätzliche Infos zu @ziggystar Kommentar
Ich habe es aufgegeben, es zu versuchen, stateT
kann jemand anderes zeigen, ob StateFreq
oder StateRandom
kann erweitert werden, um die kombinierte Berechnung durchzuführen. Stattdessen habe ich festgestellt, dass die Zusammensetzung der beiden Zustandstransformatoren folgendermaßen kombiniert werden kann:
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
}
Es basiert auf g
einer Ein-Parameter-Funktion, die das Ergebnis des ersten Zustandstransformators aufnimmt und einen Zustandstransformator zurückgibt. Dann würde folgendes funktionieren:
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
Monade nicht wirklich ein "Staatstransformator"? Und als zweite Frage: Gibt es eine schönere Möglichkeit, das Würfeln und das Summieren in einer einzigen Staatsmonade zu kombinieren? Wie würden Sie das angesichts der beiden Monaden machen?StateFreq
undStateRandom
sind Monaden. Ich denke nicht, dassState[S, x]
es sich um einen Monadentransformator handelt, daS
es sich nicht um eine Monade handeln muss. Für eine schönere Art zu kombinieren, frage ich mich auch. Ich sehe nichts offensichtlich sofort verfügbar. VielleichtstateT
könnte es helfen, aber ich habe es noch nicht herausgefunden.State[S, x]'
Objekte halten keinen Zustand, sondern eine Transformation des letzteren. Es ist nur so, dass ich denke, der Name könnte weniger verwirrend gewählt werden. Das ist nichts über deine Antwort, sondern über Scalaz.stateT
das Rollen und Summieren in einer einzigenStateT
Monade kombinieren kann ! Siehe stackoverflow.com/q/7782589/257449 . Ich bin gegen Ende festgefahren, dann habe ich estraverse
irgendwann herausgefunden.!
ist jetzteval
;~>
ist jetztexec
.Ich bin auf einen interessanten Blog-Beitrag von Grok Haskell Monad Transformers von sigfp gestoßen, der ein Beispiel für die Anwendung von zwei Zustandsmonaden über einen Monadentransformator enthält. Hier ist eine Scalaz-Übersetzung.
Das erste Beispiel zeigt eine
State[Int, _]
Monade:val test1 = for { a <- init[Int] _ <- modify[Int](_ + 1) b <- init[Int] } yield (a, b) val go1 = test1 ! 0 // (Int, Int) = (0,1)
Ich habe hier also ein Beispiel für die Verwendung von
init
undmodify
. Nachdem Sie ein wenig damit gespielt haben,init[S]
stellt sich heraus, dass es sehr praktisch ist, einenState[S,S]
Wert zu generieren , aber das andere, was es erlaubt, ist, zum Verständnis auf den Status innerhalb des zuzugreifen.modify[S]
ist eine bequeme Möglichkeit, den Zustand innerhalb des Verständnisses zu transformieren. Das obige Beispiel kann also wie folgt gelesen werden:a <- init[Int]
: Beginnen Sie mit einemInt
Status, legen Sie ihn als den von derState[Int, _]
Monade umschlossenen Wert fest und binden Sie ihn ana
_ <- modify[Int](_ + 1)
: Erhöhen Sie denInt
Statusb <- init[Int]
: nimm denInt
Status und binde ihn anb
(wie für,a
aber jetzt wird der Status erhöht)State[Int, (Int, Int)]
Wert mita
und ergebenb
.Der für das Verständnis Syntax schon macht es trivial zu Arbeiten an der
A
SeiteState[S, A]
.init
,modify
,put
Undgets
einige Werkzeuge zum Arbeiten an der bereitzustellenS
SeiteState[S, A]
.Das zweite Beispiel im Blog-Beitrag lautet:
val test2 = for { a <- init[String] _ <- modify[String](_ + "1") b <- init[String] } yield (a, b) val go2 = test2 ! "0" // (String, String) = ("0","01")
Sehr die gleiche Erklärung wie
test1
.Das dritte Beispiel ist schwieriger und ich hoffe, es gibt etwas Einfacheres, das ich noch nicht entdeckt habe.
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")
Kümmert sich in diesem Code
stTrans
um die Transformation beider Zustände (Inkrementieren und Suffixieren mit"1"
) sowie um das Herausziehen desString
Zustands.stateT
ermöglicht es uns, eine Zustandstransformation für eine beliebige Monade hinzuzufügenM
. In diesem Fall ist der StatusInt
inkrementiert. Wenn wirstTrans ! 0
anrufen würden, würden wir am Ende mitM[String]
. In unserem BeispielM
istStateString
, also werden wir mitStateString[String]
dem enden, was istState[String, String]
.Der schwierige Teil hier ist, dass wir den
Int
Zustandswert herausziehen wollenstTrans
. Dafür ist dainitT
. Es wird lediglich ein Objekt erstellt, das den Zugriff auf den Status auf eine Weise ermöglicht, mit der wir flatMap verwenden könnenstTrans
.Bearbeiten: Es stellt sich heraus, dass all diese Unbeholfenheit vermieden werden kann, wenn wir sie wirklich wiederverwenden
test1
undtest2
die gewünschten Zustände bequem im_2
Element ihrer zurückgegebenen Tupel speichern :// same as test3: val test31 = stateT[StateString, Int, (Int, String)]{ i => val (_, a) = test1 ! i for (t <- test2) yield (a, (a, t._2)) }
quelle
Hier ist ein sehr kleines Beispiel, wie
State
verwendet werden kann:Definieren wir ein kleines "Spiel", in dem einige Spieleinheiten gegen den Boss kämpfen (der auch eine Spieleinheit ist).
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))) }
Wenn das Spiel läuft, möchten wir den Spielstatus verfolgen. Definieren wir also unsere "Aktionen" in Form einer Statusmonade:
Lassen Sie uns den Boss hart schlagen, damit er 10 von seinem verliert
health
:def strike : State[Game, Unit] = modify[Game] { s => s.copy( boss = s.boss.copy(health = s.boss.health - 10) ) }
Und der Chef kann zurückschlagen! Wenn er es tut, verliert jeder in einer Gruppe 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) }
Jetzt können wir komponieren diese Aktionen in
play
:def play = for { _ <- strike _ <- fireBreath _ <- fireBreath _ <- strike } yield ()
Natürlich wird das Spiel im wirklichen Leben dynamischer sein, aber es ist Essen genug für mein kleines Beispiel :)
Wir können es jetzt ausführen, um den endgültigen Status des Spiels zu sehen:
val res = play.exec(Game.init) println(res) >> Game(0,GameUnit(80),List(GameUnit(10)))
Also haben wir den Boss kaum getroffen und eine der Einheiten ist gestorben, RIP.
Der Punkt hier ist die Zusammensetzung .
State
(was nur eine Funktion istS => (A, S)
) ermöglicht es Ihnen, Aktionen zu definieren, die zu Ergebnissen führen, und auch einen Zustand zu manipulieren, ohne zu viel zu wissen, woher der Zustand kommt. DerMonad
Teil gibt Ihnen Komposition, damit Ihre Aktionen komponiert werden können:A => State[S, B] B => State[S, C] ------------------ A => State[S, C]
und so weiter.
PS Was Unterschiede zwischen
get
,put
undmodify
:modify
kann alsget
undput
zusammen gesehen werden:def modify[S](f: S => S) : State[S, Unit] = for { s <- get _ <- put(f(s)) } yield ()
oder einfach
def modify[S](f: S => S) : State[S, Unit] = get[S].flatMap(s => put(f(s)))
Wenn Sie also verwenden, verwenden
modify
Sie konzeptionellget
undput
, oder Sie können sie einfach alleine verwenden.quelle