Pregunta Ejemplos de mónada del estado de Scalaz


No he visto muchos ejemplos de la mónada de estado scalaz. Ahi esta este ejemplo pero es difícil de entender y solo hay uno otra pregunta en el desbordamiento de la pila, parece.

Voy a publicar algunos ejemplos con los que he jugado, pero agradecería otros. También si alguien puede dar un ejemplo sobre por qué init, modify, put y gets se utilizan para eso sería genial.

Editar: aquí es una impresionante presentación de 2 horas sobre la mónada estatal.


74
2017-10-12 02:56


origen


Respuestas:


Asumo, scalaz 7.0.x y las siguientes importaciones (mira el historial de respuestas para scalaz 6.x)

import scalaz._
import Scalaz._

El tipo de estado se define como State[S, A] dónde S es tipo de 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 State[S, A] función:

// 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 de 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 enhebrar mediante llamadas a funciones. Para hacer esto en lugar de Function[A, B]definir Function[A, State[S, B]]]. Utilizar el State función...

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

Entonces el for/yield la sintaxis se puede usar 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í hay otro ejemplo. Completa 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 usar sequenceU que deducirá 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 sumas 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 poligonal para aplicar freqSum encima 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 sucintamente mediante el uso 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 porque State[S, A] es un alias tipo para StateT[Id, S, A], tenDoubleThrows2 termina tipeado como Id. yo suelo copoint para volver a convertirlo en un List tipo.

En resumen, parece que la clave para usar el estado es tener funciones que devuelvan una función que modifique el estado y el valor de resultado real deseado ... Descargo de responsabilidad: nunca he usado state en el código de producción, solo tratando de sentirlo.

Información adicional sobre @ziggystar comment

Dejé de intentar usar stateT puede ser que otra persona pueda mostrar si StateFreq o StateRandom se puede aumentar para realizar el cálculo combinado. Lo que encontré en su lugar es que la composición de los dos transformadores de estado se puede combinar así:

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 siendo una función de un parámetro tomando el resultado del primer transformador de estado y devolviendo un transformador de estado. Entonces, lo 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]()))

81
2017-10-12 03:15



Me encontré con una publicación de blog interesante Grok Haskell Monad Transformers de sigfp que tiene un ejemplo de aplicar dos mónadas de estado a través de un transformador de mónada. Aquí hay una traducción scalaz.

los primer ejemplo Muestra un State[Int, _] monada:

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 tengo aquí un ejemplo de uso init y modify. Después de jugar con eso un poco, init[S] resulta ser muy conveniente para generar un State[S,S]valor, pero lo otro que permite es acceder al estado dentro de la comprensión. modify[S] es una forma conveniente de transformar el estado dentro de la comprensión. Entonces, el ejemplo anterior se puede leer como:

  • a <- init[Int]: comienza con un Int estado, establézcalo como el valor envuelto por State[Int, _] Mónada y vincularlo a a
  • _ <- modify[Int](_ + 1): incrementa el Int estado
  • b <- init[Int]: toma el Int estado y vincularlo a b (lo mismo que para a pero ahora el estado se incrementa)
  • rendir un State[Int, (Int, Int)] valor usando a y b.

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

los segundo ejemplo en la publicación del blog se traduce a:

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

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

Casi la misma explicación que test1.

los 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 sufijo con "1"), así como sacar el String estado. stateT nos permite agregar transformación de estado en una mónada arbitraria M. En este caso, el estado es un Int eso se incrementa Si llamamos stTrans ! 0 terminaríamos con M[String]. En nuestro ejemplo, M es StateString, así que terminaremos con StateString[String] cual es State[String, String].

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

Editar: Resulta que toda esa incomodidad se puede evitar si realmente reutilizamos test1 y test2 que almacena convenientemente los estados buscados en el _2 elemento 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))
}

15
2017-10-16 02:52



Aquí hay un pequeño ejemplo de cómo State puede ser usado:

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 la obra está activa, queremos hacer un seguimiento del estado del juego, así que vamos a definir nuestras "acciones" en términos de una mónada de estado:

Golpeemos al jefe con fuerza para que pierda 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 devolver el golpe! 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, la obra será más dinámica, pero es suficiente 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 el 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. los Monad parte te da composición para que tus acciones se puedan componer:

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

y así.

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

Entonces cuando usas modify Ustedes usan conceptualmente get y put, o puedes usarlos solos.


11
2017-12-22 04:47