2011-10-12 33 views
72

No he visto muchos ejemplos de la mónada de estado scalaz. No es this example, pero es difícil de entender y solo hay un other question en desbordamiento de pila que parece.Ejemplos de mónada de estado de Scalaz

Voy a publicar algunos ejemplos con los que he jugado pero agradeceré otros. Además, si alguien puede dar un ejemplo sobre por qué se usan init, modify, put y gets, sería genial.

Editar: here es una impresionante presentación de 2 horas en la mónada de estado.

Respuesta

80

Asumo, scalaz 7.0.x y las siguientes importaciones (mirar la historia respuesta para scalaz 6.x):

import scalaz._ 
import Scalaz._ 

El tipo de estado se define como State[S, A] donde S es escribir del estado y A es el tipo del valor que se está decorando. La sintaxis básica para crear un valor de estado hace uso de la función State[S, A]:

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

Para ejecutar el cálculo del estado en un valor inicial:

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

El estado se puede pasar por las llamadas a funciones. Para hacer esto en lugar de Function[A, B], defina Function[A, State[S, B]]]. Utilice la función State ...

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

A continuación, la sintaxis for/yield se puede utilizar para componer funciones:

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) 

Aquí es otro ejemplo. Complete una lista con TwoDice() cálculos de estado.

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

Usa la secuencia para obtener un State[Random, List[(Int,Int)]]. Podemos proporcionar un alias tipo.

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

O podemos utilizar sequenceU que inferir los tipos:

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

Otro ejemplo con State[Map[Int, Int], Int] para calcular la frecuencia de las cantidades en la lista anterior. freqSum calcula la suma de los lanzamientos y las frecuencias de conteos.

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

Ahora usa transversal para aplicar freqSum sobre tenDoubleThrows. traverse es equivalente a 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]] 

O más brevemente utilizando traverseU para inferir los tipos:

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

Tenga en cuenta que debido a State[S, A] es un alias de tipo para StateT[Id, S, A], tenDoubleThrows2 termina siendo escrita como Id. Yo uso copoint para volver a convertirlo en un tipo List.

En resumen, parece que la clave para utilizar el estado es tener funciones que devuelven una función modificar el estado y el valor verdadero resultado deseado ... responsabilidad: nunca he utilizado state en el código de producción, tratando de obtener una sentir por eso.

información adicional sobre @ziggystar comentario

me dio por vencido en tratar usando stateT puede ser otra persona puede mostrar si StateFreq o StateRandom se puede aumentar para realizar el cálculo combinado. Lo que encontré en cambio, es que la composición de los dos transformadores de estado se puede combinar de esta manera:

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 
} 

Se basa en g que es una función de un parámetro tomando el resultado del primer transformador estado y devolver un transformador estado. A continuación, el siguiente funcionaría:

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

¿No es la mónada 'State' no un" transformador de estado "en realidad? Y como segunda pregunta: ¿hay alguna forma más agradable de combinar los dados rodando y la suma en una única mónada de estado? ¿Cómo harías eso dadas las dos mónadas? – ziggystar

+0

@ziggystar, técnicamente 'StateFreq' y' StateRandom' son mónadas. No creo que 'State [S, x]' sea un transformador de mónada ya que 'S' no necesita ser una mónada. Para una mejor manera de combinar, me pregunto también. No veo nada obviamente disponible. Puede ser 'stateT' podría ayudar, pero aún no lo he descubierto. – huynhjl

+0

No escribí "transformador de mónada" sino "transformador de estado". Los objetos 'State [S, x] '' no tienen un estado sino una transformación de este último. Es solo que creo que el nombre podría ser elegido menos confuso. No se trata de tu respuesta, sino de Scalaz. – ziggystar

15

me encontré con un blog interesante Grok Haskell Monad Transformers de siGFP que tiene un ejemplo de la aplicación de dos mónadas estatales a través de un transformador mónada. Aquí hay una traducción scalaz.

El primer ejemplo muestra unaState[Int, _] mónada:

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

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

Así que he aquí un ejemplo del uso init y modify. Después de jugar un poco, init[S] resulta realmente conveniente para generar un valor de State[S,S], pero lo otro que permite es acceder al estado dentro del para la comprensión. modify[S] es una forma conveniente de transformar el estado dentro de para la comprensión. Así que el ejemplo anterior se puede leer como:

  • a <- init[Int]: comenzar con un estado Int, establecerlo como el valor envuelta por el State[Int, _] mónada y enlazarlo a a
  • _ <- modify[Int](_ + 1): incrementar el estado Int
  • b <- init[Int]: tomar el estado Int y enlazarlo a b (igual que para a pero ahora se incrementa el estado)
  • dió un USI State[Int, (Int, Int)] valor ng a y b.

La sintaxis de comprensión ya hace que sea trivial trabajar en el lado A en State[S, A].init, modify, put y gets proporcionan algunas herramientas para trabajar en el lado S en State[S, A].

El segundo ejemplo en la entrada del blog se traduce en:

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

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

en gran medida la misma explicación que test1.

El tercer ejemplo es más complicado y espero que haya algo más simple que aún tengo que descubrir.

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

En ese código, stTrans se ocupa de la transformación de ambos estados (incremento y sufijos con "1"), así como sacando el estado String. stateT nos permite agregar la transformación de estado en una mónada arbitraria M. En este caso, el estado es un Int que se incrementa. Si llamamos al stTrans ! 0, terminaríamos con M[String]. En nuestro ejemplo, M es StateString, por lo que terminaremos con StateString[String] que es State[String, String].

La parte difícil aquí es que queremos sacar el valor de estado Int de stTrans. Esto es para lo que es initT. Simplemente crea un objeto que da acceso al estado de una manera que podemos flatMap con stTrans.

Editar: Resulta que toda esa incomodidad se pueden evitar si realmente reutilizamos test1 y test2 que convenientemente almacenar los estados deseados en el elemento _2 de sus tuplas devueltas:

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

Aquí está un ejemplo muy pequeño sobre cómo se puede usar State:

Definamos un pequeño "juego" donde algunas unidades de juego luchan contra el jefe (que también es una unidad de juego).

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

Cuando el juego está en que queremos hacer un seguimiento del estado del juego, por lo que vamos a definir nuestras "acciones" en términos de una mónada Estado:

Vamos a golpear el jefe duro por lo que pierde el 10 de su health:

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

Y el jefe puede contraatacar! Cuando lo hace, todos en una fiesta pierden 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) 
} 

Ahora podemos componer estas acciones en play:

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

Por supuesto, en la vida real, el juego será más dinámico, pero es suficiente comida para mi pequeño ejemplo :)

podemos ejecutarlo ahora para ver el estado final del juego:

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

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

Así que apenas golpeamos al jefe y una de las unidades murió, RIP.

El punto aquí es composición. State (que es solo una función S => (A, S)) le permite definir acciones que producen resultados y también manipular algún estado sin saber demasiado de dónde viene el estado. La parte Monad le da la composición por lo que sus acciones pueden estar compuestos:

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

y así sucesivamente.

P.S. En cuanto a las diferencias entre get, put y modify:

modify puede ser visto como get y put juntos:

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

o simplemente

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

Así que cuando se utiliza modify que conceptualmente utiliza get y put, o puede usarlos solo.

Cuestiones relacionadas