Translate

jueves, 1 de septiembre de 2022

Mónadas en Cats parte 22

El poder de la mónada State proviene de la combinación de instancias. Los métodos map y flatMap enhebran el estado de una instancia a otra. Cada instancia individual representa una transformación de estado atómico, y su combinación representa una secuencia completa de cambios:

val step1 = State[Int, String]{ num =>

   val ans = num + 1

   (ans, s"Result of step1: $ans")

}

val step2 = State[Int, String]{ num =>

val ans = num * 2

(ans, s"Result of step2: $ans")

}

val both = for {

   a <- step1

   b <- step2

} yield (a, b)

val (state, result) = both.run(20).value

// state: Int = 42

// result: (String, String) = ("Result of step1: 21", "Result of step2 : 42")

El estado final es el resultado de aplicar ambas transformaciones en secuencia. El estado se entrelaza paso a paso, aunque no interactuamos con él.

El modelo general para usar la mónada State es representar cada paso de un cálculo como una instancia y componer los pasos usando los operadores de mónada estándar. Cats proporciona varios constructores de conveniencia para crear pasos primitivos:

• get extrae el estado como resultado;

• set actualiza el estado y devuelve unit como resultado;

• pure ignora el estado y devuelve un resultado proporcionado;

• inspect extrae el estado a través de una función de transformación;

• modify actualiza el estado utilizando una función de actualización.


val getDemo = State.get[Int]

// getDemo: State[Int, Int] = cats.data.IndexedStateT@796af713

getDemo.run(10).value

// res1: (Int, Int) = (10, 10)

val setDemo = State.set[Int](30)

// setDemo: State[Int, Unit] = cats.data.IndexedStateT@f9e66fa

setDemo.run(10).value

// res2: (Int, Unit) = (30, ())

val pureDemo = State.pure[Int, String]("Result")

// pureDemo: State[Int, String] = cats.data.IndexedStateT@439e3ee4

pureDemo.run(10).value

// res3: (Int, String) = (10, "Result")

val inspectDemo = State.inspect[Int, String](x => s"${x}!")

// inspectDemo: State[Int, String] = cats.data.IndexedStateT@77263be4

inspectDemo.run(10).value

// res4: (Int, String) = (10, "10!")

val modifyDemo = State.modify[Int](_ + 1)

// modifyDemo: State[Int, Unit] = cats.data.IndexedStateT@44ddcbfc

modifyDemo.run(10).value

// res5: (Int, Unit) = (11, ())


Podemos ensamblar estos bloques de construcción usando una comprensión. Por lo general, ignoramos el resultado de las etapas intermedias que solo representan transformaciones en el estado:


import cats.data.State

import State._

val program: State[Int, (Int, Int, Int)] = for {

a <- get[Int]

_ <- set[Int](a + 1)

b <- get[Int]

_ <- modify[Int](_ + 1)

c <- inspect[Int, Int](_ * 1000)

} yield (a, b, c)

// program: State[Int, (Int, Int, Int)] = cats.data.IndexedStateT@42c9d44a

val (state, result) = program.run(1).value

// state: Int = 3

// result: (Int, Int, Int) = (1, 2, 3000)