Exemples de monade d’état Scalaz

Je n’ai pas vu beaucoup d’exemples de monade d’état scalaz. Il y a cet exemple mais il est difficile à comprendre et il ne semble y avoir qu’une seule autre question sur le débordement de stack.

Je vais poster quelques exemples avec lesquels j’ai joué mais j’en souhaiterais d’autres. De même, si quelqu’un peut donner des exemples sur l’utilisation d’ init , de modify , de put et de recevoir, ce serait génial.

Edit: voici une présentation de 2 heures sur la monade d’état.

    Je suppose, scalaz 7.0.x et les importations suivantes (consultez l’historique des réponses pour scalaz 6.x ):

     import scalaz._ import Scalaz._ 

    Le type d’état est défini comme State[S, A]S est le type de l’état et A le type de la valeur en cours de décoration. La syntaxe de base pour créer une valeur d’état utilise la fonction State[S, A] :

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

    Pour exécuter le calcul d’état sur une valeur initiale:

     // start with state of 1, pass it to s s.eval(1) // returns result value "str" // same but only resortingeve the state s.exec(1) // 2 // get both state and value s(1) // or s.run(1) // (2, "str") 

    L’état peut être passé par des appels de fonction. Pour ce faire, au lieu de la Function[A, B] , définissez la Function[A, State[S, B]]] . Utilisez la fonction d’ State

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

    Ensuite, la syntaxe for/yield peut être utilisée pour composer des fonctions:

     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) 

    Voici un autre exemple. Remplissez une liste avec les calculs d'état TwoDice() .

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

    Utilisez la séquence pour obtenir un State[Random, List[(Int,Int)]] . Nous pouvons fournir un alias de type.

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

    Ou nous pouvons utiliser sequenceU qui en déduira les types:

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

    Un autre exemple avec State[Map[Int, Int], Int] pour calculer la fréquence des sums sur la liste ci-dessus. freqSum calcule la sum des fréquences de lancer et de comptage.

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

    Utilisez maintenant traverse pour appliquer freqSum sur tenDoubleThrows . traverse équivaut à 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]] 

    Ou plus succinctement en utilisant traverseU pour déduire les types:

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

    Notez que parce que State[S, A] est un alias de type pour StateT[Id, S, A] , tenDoubleThrows2 finit par être saisi comme Id . J'utilise copoint pour le retourner dans un type de List .

    En bref, il semble que la clé pour utiliser cet état soit d'avoir des fonctions renvoyant une fonction modifiant l'état et la valeur de résultat réelle souhaitée .

    Informations supplémentaires sur le commentaire @ziggystar

    J'ai renoncé à essayer d'utiliser stateT quelqu'un d'autre peut montrer si StateFreq ou StateRandom peuvent être augmentés pour effectuer le calcul combiné. Ce que j'ai trouvé à la place, c'est que la composition des deux transformateurs d'état peut être combinée comme suit:

     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 } 

    Cela suppose que g soit une fonction à un seul paramètre prenant le résultat du premier transformateur d'état et renvoyant un transformateur d'état. Alors ce qui suit fonctionnerait:

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

    Je suis tombé sur un blog intéressant Grok Haskell Monad Transformers de sigfp qui a un exemple d’application de deux monades d’état à travers un transformateur monad. Voici une traduction scalaz.

    Le premier exemple montre une monade 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) 

    J'ai donc ici un exemple d'utilisation d' init et de modify . Après avoir joué un peu avec elle, init[S] s'avère très pratique pour générer une valeur State[S,S] , mais il permet également d'accéder à l'état dans la compréhension. modify[S] est un moyen pratique de transformer l'état à l'intérieur de la compréhension. Ainsi, l'exemple ci-dessus peut être lu comme suit:

    • a <- init[Int] : commencez par un état Int , définissez-le comme valeur enveloppée par la monade State[Int, _] et associez-le à a
    • _ <- modify[Int](_ + 1) : incrémenter l'état Int
    • b <- init[Int] : prend l'état Int et le lie à b (comme pour a mais maintenant l'état est incrémenté)
    • donne une valeur d' State[Int, (Int, Int)] utilisant a et b .

    La syntaxe for comprehension rend déjà sortingvial le travail du côté A dans State[S, A] . init , modify , put et gets fournit des outils pour travailler du côté S dans State[S, A] .

    Le deuxième exemple dans l'article de blog se traduit par:

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

    Très test1 la même explication que test1 .

    Le troisième exemple est plus compliqué et j'espère qu'il y a quelque chose de plus simple à découvrir.

     type StateSsortingng[x] = State[Ssortingng, x] val test3 = { val stTrans = stateT[StateSsortingng, Int, Ssortingng]{ i => for { _ <- init[String] _ <- modify[String](_ + "1") s <- init[String] } yield (i+1, s) } val initT = stateT[StateString, Int, Int]{ s => (s,s).pure[StateSsortingng] } for { b <- stTrans a <- initT } yield (a, b) } val go3 = test3 ! 0 ! "0" // (Int, String) = (1,"01") 

    Dans ce code, stTrans s'occupe de la transformation des deux états (incrémentation et suffixe avec "1" ) tout en retirant l'état Ssortingng . stateT nous permet d'append une transformation d'état sur une monade arbitraire M Dans ce cas, l'état est un Int qui est incrémenté. Si on appelait stTrans ! 0 stTrans ! 0 on se retrouverait avec M[Ssortingng] . Dans notre exemple, M est StateSsortingng , nous allons donc nous retrouver avec StateSsortingng[Ssortingng] qui est State[Ssortingng, Ssortingng] .

    La partie délicate ici est que nous voulons extraire la valeur de l'état Int de stTrans . C'est à quoi initT . Cela crée simplement un object qui donne access à l'état d'une manière que nous pouvons flatMap avec stTrans .

    Edit: il est possible d'éviter toute maladresse si nous réutilisons vraiment test1 et test2 qui stockent commodément les états souhaités dans l'élément _2 de leurs tuples retournés:

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

    Voici un très petit exemple sur la façon dont l’ State peut être utilisé:

    Définissons un petit “jeu” où certaines unités de jeu combattent le boss (qui est aussi une unité de jeu).

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

    Lorsque le jeu est activé, nous voulons garder une trace de l’état du jeu, définissons donc nos “actions” en termes de monade d’état:

    Frappons le boss durement, il perd 10 points de sa health :

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

    Et le patron peut riposter! Quand il fait tout le monde dans un groupe perd 5 points de 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) } 

    Maintenant, nous pouvons composer ces actions en play :

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

    Bien sûr, dans la vraie vie, le jeu sera plus dynamic, mais c'est assez pour mon petit exemple 🙂

    Nous pouvons le lancer maintenant pour voir l'état final du jeu:

     val res = play.exec(Game.init) println(res) >> Game(0,GameUnit(80),List(GameUnit(10))) 

    Donc, nous avons à peine frappé le boss et l'une des unités est morte, RIP.

    Le point ici est la composition . State (qui est juste une fonction S => (A, S) ) vous permet de définir des actions qui produisent des résultats et manipulent également certains états sans trop savoir d'où vient l'état. La partie Monad vous donne une composition afin que vos actions puissent être composées:

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

    etc.

    PS En ce qui concerne les différences entre get , put et modify :

    modify peut être vu comme get et assemblé:

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

    ou simplement

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

    Ainsi, lorsque vous utilisez la modify vous utilisez conceptuellement get et put , ou vous pouvez simplement les utiliser seul.