Scalaz X: Mónada estado

En la entrada anterior, Scalaz IX: Mónada Reader, realicé una descripción de una mónada de lectura; en esta nueva entrada, Scalaz X: Mónada estado, realizaré la descripción de la mónada estado. Para una mejor compresión de la mónada estado, es necesario entender lo que es la mónada de lectura.

Un mónada de estado es aquella mónada que dado un estado inicial, retorna una tupla con una estado siguiente y un valor. La definición matemática la podemos definir de la siguiente manera:

S' -> (S'', R)

Siendo: S’, el estado inicial; S», el estado siguiente; y, R, el valor.

En Scalaz, la sintáxis de la mónada estado se define en el paquete scalaz.State. La mónada estado es pensada para aquellos problemas que requieran de una computación por estados.

Para realizar la explicación de la mónada estado, realicemos un ejemplo práctico. Definieremos la computación para determinar si se ha leído la palabra «01» y, para ello, necesitamos definir lo siguiente:

  1. Definir una variable estado, así como, sus posibles estados.
  2. Definir las transiciones de estado; transiciones, definidas con mónada estados.
  3. Flujo de lectura

La definición de un estado puede ser definida en un objeto de un determinado tipo, así como, los posibles estados por los que transite. Para el ejemplo, defino el tipo estado como un objeto entero y sus posibles estados. Además, defino el tipo del valor a retornar por la mónada estado.

type Estado = Int
val ESTADO_0:Int = 0 // Estado inicial
val ESTADO_1:Int = 1 // Se ha leído la letra 0
val ESTADO_2:Int = 2 // Estado final, se ha leído la palabra 01.
type Resultado = Boolean

Las transiciones entre estados están definidas con las mónadas estados, las cuales definen la funcionalidad a realizar. En nuestro problema, tenemos que definir dos transiciones: la primera, para determinar si se ha leído el 0; y, la segunda, para determinar si se ha leído el 1. Evidentemente para cada transición, es necesario pasar el valor del evento que produce la transición. En nuestro caso, las mónadas estados con las transiciones son las siguientes:

def leerCero (elemento:Int):State[Estado, Resultado] = State[Estado, Resultado] {
  case estado if ESTADO_0.equals(estado) && elemento.equals(0) => (ESTADO_1, false)
  case _ => (ESTADO_0, false)
}
def leerUno(elemento:Int):State[Estado, Resultado] = State[Estado, Resultado] {
  case estado if ESTADO_1.equals(estado) && elemento.equals(1) => (ESTADO_2, true)
  case _ => (ESTADO_1, false)
}

Un aspecto importante, es entender que el estado es inherente a la mónada estado y se asigna en la creación de la mónada;y, el valor del evento, es el valor pasado por parámetro. Así, una posible iteración es la definida en la siguiente función:

def ejemploSencillo:Unit = {
  val estado:Estado = ESTADO_0
  val (estado1, resultado1) = leerCero(0)(estado)
  println(s"estado1=${estado1} resultado1=${resultado1}")
  val (estado2, resultado2) = leerUno(1)(estado1)
  println(s"estado2=${estado2} resultado1=${resultado2}")
}

La salida por consola es la siguiente:

estado1=1 resultado1=false
estado2=2 resultado1=true

Del ejemplo anterior, se realiza la definición del estado inicial y se realizan las llamadas a las transiciones con los respectivos valores correspondientes a cada evento.

El ejemplo anterior es sencillo pero no es un caso muy real; para transformarlo, voy a definir la misma lógica determinando cuándo se ha leído desde consola la palabra «10». Para ello, defino un bucle y el conjunto de transiciones. El ejemplo queda definido en el siguiente snippet:

def ejemploIterativo: Unit = {
  var estado: Estado = ESTADO_0
  var encontrada: Boolean = false
  while(!encontrada){
    print("Introduce un numero->")
    (estado) match {
    case ESTADO_0 => {
      val evento=scala.io.StdIn.readInt()
      val (estado0, resultado0) =leerCero(evento)(estado)
      estado = estado0
    }
    case ESTADO_1 => {
      val evento=scala.io.StdIn.readInt()
      val (estado0, resultado0) = leerUno(evento)(estado)
      estado = estado0
      if(resultado0){
        // encontrada = true
        println("PALABRA ENCONTRADA")
        estado = ESTADO_0
      }
   }
 }
}

La salida por consola es la siguiente:
Introduce un numero->0
Introduce un numero->1
PALABRA ENCONTRADA
Introduce un numero->
[...]

Como se observa en el ejemplo anterior, para el curioso lector, se ha dejado comentado el flag de asignación de fin de iteración.

Para el lector interesado, las entradas que he realizado sobre Scalaz hasta la fecha son las siguientes:

Scalaz IX: Mónada Reader

En la presente entrada, Scalaz IX: Mónada Reader, realizaré una descripción de ciertos tipos de funciones y abstracciones hasta llegar a definir una Monada de lectura o Mónada Reader, mónada que permite un parámetro de entrada.

En lenguaje Scala, una función se puede definir como una variable; esta función, es traducida como una clase. En el siguiente ejemplo, se definen las funciones f1 y f2 como variables y se muestra un ejemplo de uso. Con Scalaz, estas funciones se pueden interpretar como functores, con lo cual, podemos concatenar llamadas a dichas funciones con la función map. Esta situación, nos permite ejecutar una secuencia de funciones con un parámetro de entrada.

import scalaz.Scalaz._
val f1 = (_: Int) * 2
val f2 = (_: Int) + 10
println(s"f1(2)=${f1(2)}")
println
println(s"f2(2)=${f2(2)}")
println
println(s" (f1 map f2)(2)=${(f1 map f2) (2)} ") // Función map como functor
println

La salida por consola es la siguiente:

f1(2)=4
f2(2)=12
(f1 map f2)(2)=14

Como observamos en el ejemplo anterior, podemos definir una parametro de lectura identificando el tipo de entrada, en concreto, un valor de tipo entero.

Las funciones de tipo Applicative, nos permite ejecutar varias funciones y, una vez ejecutadas, realizar un cálculo con los resultados de las aplicaciones. En el siguiente ejemplo, muestro un ejemplo de una función applicative en donde se ejecutan dos funciones (f1 y f2) y sus resultado son sumados.

import scalaz.Scalaz._
val f1Applicative = ({
  (_: Int) * 2
} |@| {
  (_: Int) + 10
}) (_ + _)
println(s"f1Applicative(2)=${f1Applicative(2)}")
println

La salida por consola es la siguiente:

f1Applicative(2)=16

Como observamos en el ejemplo anterior, podemos definir una parametro de lectura identificando el tipo de entrada, en concreto, un valor de tipo entero.

Para finalizar, podemos definir una mónada, representado con un for comprehension, que define dos cálculos: el primero, la multiplicación de un tipo entero por dos; el segundo, la suma de un valor entero mas 10; y, para finalizar, la suma de su resultado. El código de ejemplo es el siguiente:

import scalaz.Scalaz._
val f1Monada: Int => Int = for {
  a <- (_: Int) * 2
  b <- (_: Int) + 10
} yield {
  a + b
}
println(s"f1Monada=${f1Monada(2)}")
println

La salida por consola es la siguiente:

f1Monada=16

Como observamos en el ejemplo anterior, podemos definir una mónada con un valor de entrada especificando su tipo. Así, podemos interpretar que una mónada es de lectura porque podemos definir valores de entrada.

Para el lector interesado, las entradas que he realizado sobre Scalaz hasta la fecha son las siguientes:

Scalaz VIII: Construcción de funciones con parámetros con Applicative

En la tercera entrada de la serie, Scalaz III: Apply y Applicative , me centre en la descripción de las API Apply y Applicative. El API Apply nos permite aplicar funciones a functores; y, el API Applicative, permite mapear funciones con N parámetros. En la presente entrada, Scalaz VIII: Construcción de funciones con parámetros con Applicative, describiré la forma de construir funciones de funciones con parámetros.

La definición de una función que realiza la suma de dos enteros de tipo Option con Applicative se puede realizar de la siguiente forma:

println(s"(3.some |@| 5.some) {_ + _}=${(3.some |@| 5.some) {_ + _}}" )

La salida por consola es la siguiente:

(3.some |@| 5.some) {_ + _}=Some(8)

La suma de estos dos elementos es sencilla pero, si necesito aplicar una serie de funciones para un valor de entrada, necesito definir una estructura de función parecida; para ello, definiremos tantas funciones como necesitemos con la definición de cada parámetro. En los siguientes apartados, muestro unos ejemplos de funciones con dos y con tres funciones.

Definición de una función de dos funciones

La definición de una función que realiza la suma de otras dos funciones las cuales, la primera, realiza la multiplicación de un entero por 2; y, la segunda, realiza la suma de un entero mas 10, se define de la siguiente forma:

val applicativeBuilder1 = ({(_:Int) * 2} |@| {(_:Int) + 10}) {_ + _}
println(s"( {(_:Int) * 2} |@| {(_:Int) + 10}) {_ + _}=${applicativeBuilder1(2)}")
println

La salida por consola es la siguiente:

({(_:Int) * 2} |@| {(_:Int) + 10}) {_ + _}=16

Definición de una función de tres funciones

La definición de una función que realiza la suma de tres funciones las cuáles, la primera, realiza la multiplicación de un entero por 2; la segunda, realiza la suma de un entero más 10; y, la tercera, realiza la suma de un entero mas 20, se define de la siguiente forma:

val applicativeBuilder2 = ({(_:Int) * 2} |@| {(_:Int) + 10} |@| {(_:Int) + 20}) {_ + _ + _}
println(s"({(_:Int) * 2} |@| {(_:Int) + 1}) {_ + _}=${applicativeBuilder2(2)}")
println

La definición de una función que realiza la multiplicación de dos enteros y al resultado se le suma una función las cuáles, la primera, realiza la multiplicación de un entero por 2; la segunda, realiza la suma de un entero más 10; y, la tercera, realiza la suma de un entero mas 20, se define de la siguiente forma:

val applicativeBuilder3 = ({(_:Int) * 2} |@| {(_:Int) + 10} |@| {(_:Int) + 20}) {_ * _ + _}
println(s"({(_:Int) * 2} |@| {(_:Int) + 1}) {_ * _ + _}=${applicativeBuilder3(2)}")
println

La salida por consola de las funciones son las siguientes:

({(_:Int) * 2} |@| {(_:Int) + 1}) {_ + _ + _}=38
({(_:Int) * 2} |@| {(_:Int) + 1}) {_ * _ + _}=70

En el caso que necesitemos tener funciones con distintos parámetros, utilizaremos el API Applicative como el descrito en la tercera entrada de la serie.

Para el lector interesado, las entradas que he realizado sobre Scalaz hasta la fecha son las siguientes:

Scala VII: leyes matemáticas de las mónadas

En la presente entrada, Scala VII: leyes matemáticas de las mónadas, me centraré en identificar las leyes de las mónadas y unos ejemplos que demuestran dichas leyes.

Las leyes matemáticas por las que se rigen las mónadas son dos: propiedad de elemento neutro o elemento de identidad y propiedad asociativa.

La demostración de la propiedad de la propiedad del elemento neutro por la izquierda y por la derecha queda definida en el siguiente snippet de código:

import scalaz.Monad
import scalaz.Scalaz._
def identidadPorLaIzquierda(): Unit = {
  Monad[Option].point("izquierda").>>=({ x => (x + " OK").some }).assert_===("izquierda OK".some)
}
def identidadPorLaDerecha(): Unit = {
  (("OK").some).>>=( x => Monad[Option].point(x + " derecha")).assert_===("OK derecha".some)
}

La propiedad  asociativa para la mónada queda definda en el siguiente snippet:

def asociatividad(): Unit = {
  Monad[Option].point(4).>>=({ x => (x + 4).some }).>>=({ y => ( y + 2 ).some }).assert_===(
  Monad[Option].point(4).>>=({ x => (x + 2).some }).>>=({ y => ( y + 4 ).some }) )
}

Como se observa en el código anterior, el resultado de cálculo de las funciones monádicas es el mismo ; con lo cual, el orden de ejecución de las funciones de suma de 4 y de 2 es el mismo.

Para el lector interesado, las entradas que he realizado sobre Scalaz hasta la fecha son las siguientes: