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:

Scalaz VI: continuación de mónadas

En la presente entrada, Scalaz VI: continuación de mónadas, continuaré comentando detalles de las Mónadas con Scalaz: mónadas y case class, funciones lambdas monádicas, listas monádicas, MonadPlus, Plus y PlusEmpty.

1.- Mónadas y case class

Las case class son aquellas clases con unas particularidades en referencia a una clase normal. Una case class dispone de algún método añadido y un companion object. Un ejemplo de case class puede ser el siguiente:

case class Alumno( nombre: String, apellido:String, edad:Int, curso: Curso)

En los siguientes apartados, trataremos de ver el comportamiento de las case class desde un punto de vista monádico.

1.1.- Case Class no monádica

Para nuestro ejemplo, vamos a definir un case class que represente una balanza; esta balanza, contiene valor a su izquierda y a su derecha de tipo enteros. Además, de los métodos propios, vamos a definir dos funciones para incrementar el valor de la izquierda y el valor de la derecha. La definición es la siguiente:

type Peso = Int
case class Balanza(izquierda: Peso, derecho: Peso) {
  def asignarIzquierda(peso: Peso): Balanza = copy(izquierda = izquierda + peso)
  def asignarDerecha(peso: Peso): Balanza = copy(derecho = derecho + peso)
}

El proceso de creación de la case class Balanza con unos valores iniciales y la asignación de los pesos izquierdo y derecho, quedan descritos en los siguientes ejemplos:

println(s"Balanza(0,0).asignarIzquierda(2)=${Balanza(0, 0).asignarIzquierda(2)}")
println(s"Balanza(0,0).asignarDerecha(2)=${Balanza(0, 0).asignarDerecha(2)}")
println(s"Balanza(1,2).asignarIzquierda(2)=${Balanza(1, 2).asignarIzquierda(2)}")
println(s"Balanza(1,2).asignarDerecha(2)=${Balanza(1, 2).asignarDerecha(2)}")
println(s"Balanza(0,0).asignarIzquierda(2).asignarIzquierda(2).asignarDerecha(2)=${Balanza(0, 0).asignarIzquierda(2).asignarIzquierda(2).asignarDerecha(2)}")

La salida por consola es la siguiente:

Balanza(0,0).asignarIzquierda(2)=Balanza(2,0)
Balanza(0,0).asignarDerecha(2)=Balanza(0,2)
Balanza(1,2).asignarIzquierda(2)=Balanza(3,2)
Balanza(1,2).asignarDerecha(2)=Balanza(1,4)
Balanza(0,0).asignarIzquierda(2).asignarIzquierda(2).asignarDerecha(2)=Balanza(4,2)

En los ejemplos anteriores, instanciamos una clase que no es monádica pero podemos asignar tantas veces como queramos los valores internos. En el siguiente apartado, realizaremos las mismas operaciones pero definiendo la clase como monádica.

1.2.- Case class monádica

Para que la case class Balanza sea monádica, debemos definir la case class con métodos que retornen tipos monádicos; y, para poder realizarlo, los métodos asignarIzquierda y asignarDerecha, los definimos con un tipo de retorno monádico como es el tipo Option. Así, la definición de la case class Balanza de forma monádica queda definida de la siguiente forma:

case class BalanzaOption(izquierda: Peso, derecho: Peso) {
  def asignarIzquierda(peso: Peso): Option[BalanzaOption] =
    if (peso > 0)
      copy(izquierda = izquierda + peso).some
    else none
  def asignarDerecha(peso: Peso): Option[BalanzaOption] =
    if (peso > 0)
      copy(derecho = derecho + peso).some
    else none
}

El proceso de creación de la case class BalanzaOption se puede realizar de una forma clásica, o bien, utilizando la entidad Monad. En los siguiente snippet se definen un conjunto de ejemplos:

import scalaz.Monad
import scalaz.Scalaz._
println(s"BalanzaOption(0,0).asignarIzquierda(2)=${BalanzaOption(0, 0).asignarIzquierda(2)}")
println(s"BalanzaOption(0,0).asignarDerecha(2)=${BalanzaOption(0, 0).asignarDerecha(2)}")
println(s"{BalanzaOption(0,0).asignarIzquierda(2).flatMap(_.asignarDerecha(2)).flatMap(_.asignarIzquierda(1))=" +
s"${BalanzaOption(0, 0).asignarIzquierda(2).flatMap(_.asignarDerecha(2)).flatMap(_.asignarIzquierda(1))}")
println(s"Monad[Option].point(BalanzaOption(0,0)) >>= {_.asignarIzquierda(3)} >>= {_.asignarDerecha(9)} }=" +
  s"${
    Monad[Option].point(BalanzaOption(0, 0)) >>= {
    _.asignarIzquierda(3)
  } >>= {
    _.asignarDerecha(9)
  }
}")

La salida por consola es la siguiente:

BalanzaOption(0,0).asignarIzquierda(2)=Some(BalanzaOption(2,0))
BalanzaOption(0,0).asignarDerecha(2)=Some(BalanzaOption(0,2))
{BalanzaOption(0,0).asignarIzquierda(2).flatMap(_.asignarDerecha(2)).flatMap(_.asignarIzquierda(1))=Some(BalanzaOption(3,2))
Monad[Option].point(BalanzaOption(0,0)) >>= {_.asignarIzquierda(3)} >>= {_.asignarDerecha(9)} }=Some(BalanzaOption(3,9))

Como observamos en el penúltimo ejemplo, definimos la case class de forma normal y, al invocar los métodos de asignación, incrementamos los valores izquiero y derecho utilizando la función flatMap.

En el último ejemplo, instanciamos la clase BalanzaOption aplicando la propiedad unaria del interfaz Monad y, para cada incremento, aplicamos la función alias >>= de la función flatMap.

2.- Funciones lambdas monádicas

Conforme a la definición realizada en la entrada «Scala V: Mónadas, introducción», una mónada permite la ejecución de una sucesión de operaciones. Así, podemos defenir la definición de una secuencia de funciones lambdas de estructuras monádicas. A continuación, se muestra un conjunto de ejemplos de funciones lambdas monádicas:

import scalaz.Monad
import scalaz.Scalaz._
println(s"3.some >>= { x => '!'.some >>= { y => (x.shows + y).some } }= ${3.some >>= { x => "!".some >>= { y => (x.shows + y).some } }}")
println(s"Operación (3+5)=? ==>> ${ 3.some >>= { x => "+".some >>= { y => 5.some >>= { z => (x.shows + y + z.shows + "=" + (x.toInt+z.toInt)).some }}} }")
println(s"(none: Option[String]) >>= { x => '!'.some >>= { y => (x.shows + y).some } }= ${(none: Option[String]) >>= { x => "!".some >>= { y => (x.shows + y).some } }}")
println(s"3.some >>= { x => (none: Option[String]) >>= { y => (x.shows + y).some } }= ${3.some >>= { x => (none: Option[String]) >>= { y => (x.shows + y).some } }}")
println(s"3.some >>= { x => "!".some >>= { y => (none: Option[String]) } }= ${3.some >>= { x => "!".some >>= { y => (none: Option[String]) } }}")
println(s"Monad[Option].point(3).>>=({x => '!'.some}).>>=({ y =>none: Option[String] })= ${Monad[Option].point(3).>>=({x => "!".some}).>>=({ y => none: Option[String] })}")

La salida por consola es la siguiente:

3.some >>= { x => '!'.some >>= { y => (x.shows + y).some } }= Some(3!)
Operación (3+5)=? ==>> Some(3+5=8)
(none: Option[String]) >>= { x => '!'.some >>= { y => (x.shows + y).some } }= None
3.some >>= { x => (none: Option[String]) >>= { y => (x.shows + y).some } }= None
3.some >>= { x => "!".some >>= { y => (none: Option[String]) } }= None
Monad[Option].point(3).>>=({x => '!'.some}).>>=({ y =>none: Option[String] })= None

A continuación, se muestran ejemplos con case class:

import scalaz.Monad
import scalaz.Scalaz._
println(s"Monad[Option].point(BalanzaOption(0,0)).>>=({_.asignarIzquierda(2)})=> ${Monad[Option].point(BalanzaOption(0,0)).>>=({_.asignarIzquierda(2)})} ")
println
println(s"Monad[Option].point(BalanzaOption(0,0)).>>=({_.asignarIzquierda(2)}).>>=({_.asignarDerecha(6)}) => " +
s"${Monad[Option].point(BalanzaOption(0,0)).>>=({_.asignarIzquierda(2)}).>>=({_.asignarDerecha(6)}) } ")
println
println(s"Monad[Option].point(BalanzaOption(0,0)).>>( {none: Option[BalanzaOption]} ).>>=({_.asignarDerecha(6)}) => " +
 s"${Monad[Option].point(BalanzaOption(0,0))
  .>>( {none: Option[BalanzaOption]} )
  .>>=({_.asignarDerecha(6)}) } ")
println
def routineOK: Option[BalanzaOption] =
  for {
    start <- Monad[Option].point(BalanzaOption(0, 0))
    first <- start.asignarIzquierda(2)
    second <- first.asignarDerecha(2)
    third <- second.asignarIzquierda(1)
} yield third
println(s"routineOK= ${routineOK}")
println
def routineKO: Option[BalanzaOption] =
for {
  start <- Monad[Option].point(BalanzaOption(0, 0))
  first <- start.asignarIzquierda(2)
  _ <- (none: Option[BalanzaOption])
  second <- first.asignarDerecha(2)
  third <- second.asignarIzquierda(1)
} yield third
println(s"routineKO= ${routineKO}")
println

La salida por consola es la siguiente:

Monad[Option].point(BalanzaOption(0,0)).>>=({_.asignarIzquierda(2)})=> Some(BalanzaOption(2,0)) 
Monad[Option].point(BalanzaOption(0,0)).>>=({_.asignarIzquierda(2)}).>>=({_.asignarDerecha(6)}) => Some(BalanzaOption(2,6)) 
Monad[Option].point(BalanzaOption(0,0)).>>( {none: Option[BalanzaOption]} ).>>=({_.asignarDerecha(6)}) => None 
routineOK= Some(BalanzaOption(3,2))
routineKO= None

3.- Listas monádicas

En la ejecución de una función dentro de una mónada, podemos tener como resultado una lista y, esta lista, puede ser combinada con otras lista. Un ejemplo para ilustrar esta causística es la siguiente:

val resultado1 = for {
  n <- List(1, 2)
  ch <- List('a', 'b', 'c')
} yield (n, ch)
println(s"resultado1=${resultado1}")
println

La salida por consola es la siguiente:

resultado1=List((1,a), (1,b), (1,c), (2,a), (2,b), (2,c))

El ejemplo anterior definido con for comprehension, se puede realizar con las fuciones de la sintáxis Scalaz de la siguiente manera:

import scalaz.Monad
import scalaz.Scalaz._
println(s"^(List(1, 2), List('a', 'b', 'c')){ _ * _} = ${ ^(List(1, 2), List('a', 'b', 'c')){ (a:Int, b:Char) => (a.toString, b.toString) } }")
println

La salida por consola es la siguiente:

^(List(1, 2), List('a', 'b', 'c')){ _ * _} = List((1,a), (1,b), (1,c), (2,a), (2,b), (2,c))

Otros ejemplos pueden ser los siguientes:

import scalaz.Monad
import scalaz.Scalaz._
println(s"^(List(1, 2, 3), List(10, 100, 100)) {_ * _}=${^(List(1, 2, 3), List(10, 100, 100)) {_ * _}}")
println
println(s"^(List(1, 2, 3), List(10, 100, 1000, 10000)) {_ * _}=${^(List(1, 2, 3), List(10, 100, 1000, 10000)) {_ * _}}")
println
println(s"^^(List(1, 2, 3), List(10, 100, 1000, 10000), List(3,2,1)) {_ * _ * _}=${^^(List(1, 2, 3), List(10, 100, 100, 1000), List(3,2,1)) {_*_*_}}")
println
println(s"^(List(1, 2, 3), List(10, 100, 100)) { (elem1:Int, elem2:Int) => (elem1*100) * elem2 }=${^(List(1, 2, 3), List(10, 100, 100)) { (elem1:Int, elem2:Int) => (elem1*100) * elem2 }}")
println
println(s"List(3, 4, 5) >>= {x => List(x, -x)}=${List(3, 4, 5) >>= {x => List(x, -x)}}")
println

La salida por pantalla es la siguiente:

^(List(1, 2, 3), List(10, 100, 100)) {_ * _}=List(10, 100, 100, 20, 200, 200, 30, 300, 300)
^(List(1, 2, 3), List(10, 100, 1000, 10000)) {_ * _}=List(10, 100, 1000, 10000, 20, 200, 2000, 20000, 30, 300, 3000, 30000)
^^(List(1, 2, 3), List(10, 100, 1000, 10000), List(3,2,1)) {_ * _ * _}=List(30, 20, 10, 300, 200, 100, 300, 200, 100, 3000, 2000, 1000, 60, 40, 20, 600, 400, 200, 600, 400, 200, 6000, 4000, 2000, 90, 60, 30, 900, 600, 300, 900, 600, 300, 9000, 6000, 3000)
^(List(1, 2, 3), List(10, 100, 100)) { (elem1:Int, elem2:Int) => (elem1*100) * elem2 }=List(1000, 10000, 10000, 2000, 20000, 20000, 3000, 30000, 30000)
List(3, 4, 5) >>= {x => List(x, -x)}=List(3, -3, 4, -4, 5, -5)

4.- MonadPlus

El type clase MonadPlus es una mónada que puede actuar como monoide.

Desde un punto de vista genérico, un monoide es aquella definición de un interfaz en el cual se define un valor base y una función binaria a partir de un conjunto de datos. Un monoide es una abstración de una HOF (Higuer Order Function).

Un ejemplo de MonadPlus es el siguiente:

import scalaz.Monad
import scalaz.Scalaz._
val resultado1 =
for {
  x <- 1 |-> 50 if x.shows contains '7'
} yield x
println(s"resultado1=${resultado1}")
println()
println(s"Filtrado:(1 |-> 50) filter { x => x.shows contains '7' }=${(1 |-> 50) filter { x => x.shows contains '7' }} ")

La salida por consola es la siguiente:

resultado1=List(7, 17, 27, 37, 47)
Filtrado:(1 |-> 50) filter { x => x.shows contains '7' }=List(7, 17, 27, 37, 47)

5.- Plus, PlusEmpty

En Scalaz disponemos de las type clases Plus, PlusEmpty y ApplicativePlus las cuales tienen la siguiente definición:

trait Plus[F[_]] { self =>
def plus[A](a: F[A], b: => F[A]): F[A]
}
trait PlusEmpty[F[_]] extends Plus[F] { self =>
////
def empty[A]: F[A]
}
trait ApplicativePlus[F[_]] extends Applicative[F] with PlusEmpty[F] { self =>
...
}

La type class PluEmpty define un elemento que corresponde con el elemento vacío; y, la type class Plus, defien una función para realizar una unión de elementos del mismo tipo. De la misma manera que otras type class, se definen sintáxis de esta funcionalidad; en nuestro caso, definidas en PlusOps la cual define la función <+> de la función plus.

import scalaz.Monad
import scalaz.Scalaz._
println(s"List(1, 2, 3) <+> List(4, 5, 6)= ${List(1, 2, 3) <+> List(4, 5, 6)}")

La salida por consola es la siguiente:

List(1, 2, 3) <+> List(4, 5, 6)= List(1, 2, 3, 4, 5, 6)

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

 

Scalaz V: introducción a Mónadas

En la presente entrada, Scalaz V: introducción a Mónadas, realizaré una descripción de qué es una mónada y cómo aplicarlo con Scalaz.

Definimos mónada como una extensión o herencia de un Functor. La mónada es la solución al siguiente problema: sea aquel valor de entrada que se ejecuta en un contexto con una función el cual genera un valor de salida; y, este valor de salida, es aplicado como valor de entrada en otro contexto para una función la cual genera un valor de salida; y, est valor de salida, es usado como entrada en otro contexto con una función la cual genera una valor de salida y, así, sucesivamente. Desde un punto de vista mas abstracto y orientado a la programación estructurada, es la sucesión de sentencias que se van ejecutando de forma secuencial.

La mónada cumple la propiedad de identidad o unitaria y la propiedad asociativa.

La definición de Mónada en Scalaz hereda de Applicative y de Bind, estudiado en las entradas de functores. La definición es la siguiente:

trait Monad[F[_]] extends Applicative[F] with Bind[F] { self =>
}

La función principal de una mónada es la función flatMap y, en Scalaz, se definen alias de funciones de la función flatMap. La definición de las operaciones se define en BindOps como sigue:

/** Wraps a value `self` and provides methods related to `Bind` */
trait BindOps[F[_],A] extends Ops[F[A]] {
  implicit def F: Bind[F]
  ////
  import Liskov.<~<
  def flatMap[B](f: A => F[B]) = F.bind(self)(f)
  def >>=[B](f: A => F[B]) = F.bind(self)(f)
  def ∗[B](f: A => F[B]) = F.bind(self)(f)
  def join[B](implicit ev: A <~< F[B]): F[B] = F.bind(self)(ev(_))
  def μ[B](implicit ev: A <~< F[B]): F[B] = F.bind(self)(ev(_))
  def >>[B](b: F[B]): F[B] = F.bind(self)(_ => b)
  def ifM[B](ifTrue: => F[B], ifFalse: => F[B])(implicit ev: A <~< Boolean): F[B] = {
    val value: F[Boolean] = Liskov.co[F, A, Boolean](ev)(self)
    F.ifM(value, ifTrue, ifFalse)
  }
 ////
}

Las importaciones necesarias para trabajar con Mónadas en Scalaz son las siguientes:

 import scalaz.Monad
 import scalaz.Scalaz.

Ejemplo de mónadas

En los siguientes apartados, se muestran unos ejemplos de mónadas con la función flatMap y sus funciones alias.

Ejemplo de función flatMap

 println(s"3.some flatMap { x => (x + 1).some } }=${ 3.some flatMap { x => (x + 1).some } }")

La salida por consola es la siguiente:

 3.some flatMap { x => (x + 1).some } }=Some(4)

Ejemplo de función >>=, alias de flatMap

 println(s"3.some >>= { x => (x + 1).some } }=${ 3.some >>= { x => (x + 1).some } }")
 val monadOption2 = Monad[Option].point("Palabra") >>= { (elem:String) => Some(elem + " lo añadido") }
 println(s"Monad[Option].point('Palabra') >>= { (elem:String) => Some(elem + ' lo añadido') }=${monadOption2}")
 val monadOption3 = Monad[Option].point(10) >>= { (elem:Int) => Some(elem + 369) }
 println(s"Monad[Int].point(10) flatMap { (elem:Int) => elem + 369 }=${monadOption3}")

La salida por consola es la siguiente:

 3.some >>= { x => (x + 1).some } }=Some(4)
 Monad[Option].point('Palabra') >>= { (elem:String) => Some(elem + ' lo añadido') }=Some(Palabra lo añadido) 
 Monad[Int].point(10) flatMap { (elem:Int) => elem + 369 }=Some(379)

Ejemplo de función ∗, alias de flatMap

 println(s"3.some ∗ { x => (x + 1).some } }=${ 3.some ∗ { x => (x + 1).some } }")

La salida por consola es la siguiente:

 3.some ∗ { x => (x + 1).some } }=Some(4)

Función unaria point de Monad.

 val monadOption1 = Monad[Option].point("Palabra")
 println(s"Monad[Option].point('Palabra') =${ monadOption1 }")

La salida por consola es la siguiente:

 Monad[Option].point('Palabra') =Some(Palabra)

Función condicional de Monad.

 val monadOption4 = Monad[Option].ifM(Some(3<4), Some("3 menor de 4"), Some("error en la condición"))
 println(s"Monad[Option].ifM(Some(3<4), Some('3 menor de 4'), Some('error en la condición'))=${monadOption4}")
 val monadOption5 = Monad[Option].ifM(Some(3>4), Some("3 menor de 4"), Some("error en la condición"))
 println(s"Monad[Option].ifM(Some(3>4), Some('3 menor de 4'), Some('error en la condición'))=${monadOption5}")

La salida por consola es la siguiente:

 Monad[Option].ifM(Some(3<4), Some('3 menor de 4'), Some('error en la condición'))=Some(3 menor de 4)
 Monad[Option].ifM(Some(3>4), Some('3 menor de 4'), Some('error en la condición'))=Some(error en la condición)

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

Scalaz IV: Tipos etiquetados, propiedad asociativa y monoides

En la presente entrada, Scalaz IV: Tipos etiquetados, propiedad asociativa y monoides, me centrará en los tipos etiquetados y realizaré una breve introducción del concepto monoide para realizar operaciones con tipos etiquetados. Los tipos etiquetados hay que verlos como un wrapper de un tipo.

Tipos Etiquetados

En Scalaz existe el elemento Tag y su correspondiente alias, representado como «@@«. Las importaciones se realizan de la siguiente forma:

import scalaz.{@@, Tag}

Desde un punto conceptual, lo podemos definir de la siguiente forma:

type Tagged[U] = { type Tag = U }
type @@[T,U] = T with Tagged[U]

Supongamos que necesitamos definir el tipo Impuesto y, en función del caso de uso, el tipo impuesto puede estar definido como valor entero o real. La forma de abordar este escenario es utilizar un tipo etiquetado. Así, definimos el tipo Impuesto de la siguiente forma:

sealed trait Impuesto

Para definir el tipo del valor, definimos el tipo etiquetado Impuesto de la siguiente forma:

def Impuesto[A](a:A): A @@ Impuesto = Tag[A, Impuesto](a)

El ejemplo anterior, define un Impuesto cuyo valor es de tipo A. Un ejemplo de creación de un impuesto del 80 por ciento lo podemos realizar de la siguiente forma:

 val impuesto1 = Impuesto(0.8)
 println(s"impuesto1=${impuesto1}")

La salida por consola es la siguiente:

 impuesto1=0.8

Este tipo es utilizado en combinación con otros tipos; por ejemplo, para el cálculo de un sueldo. Así, podemos definir el tipo etiquetado Sueldo de la siguiente forma:

 sealed trait Sueldo
 def Sueldo[A](a:A): A @@ Sueldo = Tag[A, Sueldo](a)

Si tenemos un impuesto y un sueldo de 10000, la operación de cálculo del sueldo neto a partir del impuesto es el siguiente:

 def calculoSueldoNeto(m: Double @@ Impuesto): Double @@ Sueldo = Sueldo( 10000 * Tag.unwrap(m))
 val sueldoNeto = calculoSueldoNeto( Impuesto(0.8) )
 println(s"sueldoNeto=${sueldoNeto}")

La salida por consola es la siguiente:

 sueldoNeto=8000.0

Propiedad asociativa

Según Wikipedia definimos la propiedad asociativa de la siguiente manera:

La asociatividad es una propiedad en el álgebra y la lógica proposicional que se cumple si, dados tres o más elementos cualquiera de un conjunto determinado, se verifica que existe una operación: op , que cumpla la igualdad:

A op (B op C) = (A op B) op C

Desde un punto de vista del programador en Scala, unos ejemplos de la propiedad asociativa son los siguientes:

  • Ejemplo de operaciones con enteros
(3 * 2) * ( 8 * 5) assert_=== 3 * ( 2 * ( 8 * 5))
  • Ejemplo con colecciones de String definidos en listas.
List("pr") ++ ( List("u") ++ List("eba")) assert_=== ( List("pr") ++ List("u") ) ++ List("eba")

En Scalaz, existe la posibilidad de aplicar la asociatividad utilizando la type classes SemigroupOps la cual tiene la siguiente definición:

 trait Semigroup[A] { self =>
   def append(a1: A, a2: => A): A
   [...]
 }
 
 trait SemigroupOps[A] extends Ops[A] {
  final def |+|(other: => A): A = A.append(self, other)
  final def mappend(other: => A): A = A.append(self, other)
  final def ⊹(other: => A): A = A.append(self, other)
 }

La importación de dichas funciones se realiza de la siguiente forma: import scalaz.Scalaz._

Sean un conjunto de elementos definidos por lista de enteros y cadenas. Se aplican la propiedad asociativa con las funciones del interfaz SemigroupOps de la siguiente manera:

Ejemplos con función mappend

 val result1 = List(1, 2, 3) mappend List(4, 5, 6)
 println(s"Resultado1 =${result1}")
 println
 val result2 = "uno" mappend "dos"
 println(s"Resultado2 =${result2}")
 println

La salida por consola es la siguiente:

 Resultado1 =List(1, 2, 3, 4, 5, 6)
 Resultado2 =unodos

Ejemplos con función |+|

 val result3 = List(1, 2, 3) |+| List(4, 5, 6)
 println(s"Resultado3 =${result3}")
 println
 val result4 = "uno" |+| "dos"
 println(s"Resultado4 =${result4}")
 println

La salida por consola es la siguiente:

 Resultado3 =List(1, 2, 3, 4, 5, 6)
 Resultado4 =unodos

Ejemplos con función ⊹

 val result5 = "uno" ⊹ "dos"
 println(s"Resultado5 =${result5}")
 println

La salida por consola es la siguiente:

 Resultado5 =unodos

Monoide

Un monoide es una abstracción de una función de orden superior HOF. Un monoide está compuesta por una función binaria que cumple la propiedad asociativa y una función binaria de identidad. En esta entrada, me centraré en la función binaria de identidad y su relación con los tipos etiquetados.

Una función binaria de identidad es aquella función que en una definición recursiva, corresponde con el caso base. En Scalaz, el paso base se define con ‘zero’. La definición es la siguiente:

 trait Monoid[A] extends Semigroup[A] { self =>
 ////
 /** The identity element for `append`. */
 def zero: A
 ...
 }

Unos ejemplos son los siguientes:

  • Monoide de una lista de enteros:
 val result1 = Monoid[List[Int]].zero
 println(s"Monoid[List[Int]].zero=${result1}")

La salida por consola es la siguiente:

 Monoid[List[Int]].zero=List()
  • Monoide de un String:
 val result2 = Monoid[String].zero
 println(s"Monoid[List[Int]].zero='${result2}'")

La salida por consola es la siguiente:

 Monoid[List[Int]].zero=''
  • Monoide de enteros:
 val result3 = Monoid[Int].zero
 println(s"Monoid[Int].zero='${result3}'")

La salida por consola es la siguiente:

 Monoid[Int].zero='0'
  • Monoide de un entero y la aplicación de la propiedad asociativa.
 val result4 = 10 |+| Monoid[Int].zero
 println(s"10 |+| Monoid[Int].zero='${result4}'")

La salida por consola es la siguiente:

 10 |+| Monoid[Int].zero='10'

Tags.Disjunction

El tipo etiquetado Disjunction corresponde al tipo lógico que define el operador OR. El caso base, o bien, caso zero en Scalaz corresponde con el valor FALSE.

Unos ejemplos del Tags.Disjunction son los siguientes:

 println(s"Tags.Disjunction(true)=${Tags.Disjunction(true)}")
 println(s"Tags.Disjunction(false)=${Tags.Disjunction(false)}")
 println(s"Tags.Disjunction(true) |+| Tags.Disjunction(true)=${Tags.Disjunction(true) |+| Tags.Disjunction(true)}")
 println(s"Tags.Disjunction(true) |+| Tags.Disjunction(false)=${Tags.Disjunction(true) |+| Tags.Disjunction(false)}")
 println(s"Tags.Disjunction(false) |+| Tags.Disjunction(true)=${Tags.Disjunction(false) |+| Tags.Disjunction(true)}")
 println(s"Tags.Disjunction(false) |+| Tags.Disjunction(false)=${Tags.Disjunction(false) |+| Tags.Disjunction(false)}")
 println(s"Monoid[Boolean @@ Tags.Disjunction].zero ${Monoid[Boolean @@ Tags.Disjunction].zero}")
 println(s"Monoid[Boolean @@ Tags.Disjunction].zero |+| Tags.Disjunction(true)=${Monoid[Boolean @@ Tags.Disjunction].zero |+| Tags.Disjunction(true)}")

La salida por consola es la siguiente:

 Tags.Disjunction(true)=true
 Tags.Disjunction(false)=false
 Tags.Disjunction(true) |+| Tags.Disjunction(true)=true
 Tags.Disjunction(true) |+| Tags.Disjunction(false)=true
 Tags.Disjunction(false) |+| Tags.Disjunction(true)=true
 Tags.Disjunction(false) |+| Tags.Disjunction(false)=false
 Monoid[Boolean @@ Tags.Disjunction].zero false
 Monoid[Boolean @@ Tags.Disjunction].zero |+| Tags.Disjunction(true)=true

Tags.Conjunction

El tipo etiquetado Conjunction corresponde con el tipo lógico que define el operador AND. El caso base, o bien, caso zero en Scalaz corresponde con el calor TRUE.

Unos ejemplos del Tags.Conjunction son los siguientes:

 println(s"Tags.Conjunction(true)=${Tags.Conjunction(true)}")
 println(s"Tags.Conjunction(false)=${Tags.Conjunction(false)}")
 println(s"Tags.Conjunction(true) |+| Tags.Conjunction(true)=${Tags.Conjunction(true) |+| Tags.Conjunction(true)}")
 println(s"Tags.Conjunction(true) |+| Tags.Conjunction(false)=${Tags.Conjunction(true) |+| Tags.Conjunction(false)}")
 println(s"Tags.Conjunction(false) |+| Tags.Conjunction(true)=${Tags.Conjunction(false) |+| Tags.Conjunction(true)}")
 println(s"Tags.Conjunction(false) |+| Tags.Conjunction(false)=${Tags.Conjunction(false) |+| Tags.Conjunction(false)}")
 println(s"Monoid[Boolean @@ Tags.Conjunction].zero=${Monoid[Boolean @@ Tags.Conjunction].zero}")
 println(s"Monoid[Boolean @@ Tags.Conjunction].zero |+| Tags.Conjunction(true)=${Monoid[Boolean @@ Tags.Conjunction].zero |+| Tags.Conjunction(true)}")

La salida por consola es la siguiente:

 Tags.Conjunction(true)=true
 Tags.Conjunction(false)=false
 Tags.Conjunction(true) |+| Tags.Conjunction(true)=true
 Tags.Conjunction(true) |+| Tags.Conjunction(false)=false
 Tags.Conjunction(false) |+| Tags.Conjunction(true)=false
 Tags.Conjunction(false) |+| Tags.Conjunction(false)=false
 Monoid[Boolean @@ Tags.Conjunction].zero=true
 Monoid[Boolean @@ Tags.Conjunction].zero |+| Tags.Conjunction(true)=true

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

Scalaz III: Apply y Applicative

En la primera entrada de la serie, Scalaz I: Functores y funciones como functores , realicé la descripción de functores y cómo utilizar funciones como functores. En la presente entrada, Scalaz III: Apply y Aplicative, me centraré en describir las API Apply y Applicative.

En Scalaz, Apply hereda de la Functor; y, Applicative, hereda a su vez de Apply.

Apply

El API Apply permite aplicar una función a un functor y, al estar definido mediante el patrón type classes, contiene un conjunto de funciones dentro de la sintaxis del type class.

La función principal es la función ap la cual define una función que es aplicada al functor. Así, una definición básica de Apply es la siguiente:

 trait Apply[F[_]] extends Functor[F] { self =>
   def ap[A,B](fa: => F[A])(f: => F[A => B]): F[B]
   [...]
 }

Analizando la función ap realizamos la siguiente lectura, dado un functor fa, se le aplica la función f; y,  en función del número de functores, podemos definir ap2, para dos functores, hasta ap8, para 8 functores.

Para poder operar con Apply, es necesario realizar las importaciones en el código de la siguiente manera:

import scala.language.higherKinds
import scalaz.Apply
import scalaz.Scalaz._

Ejemplo Apply con un functor

Dada una función con nombre incrementoMas2 la cual realiza el incremento en dos unidades de un entero, se aplica la función ap de Apply para realizar el incremento de un functor definido por un elemento Option de valores enteros de la siguiente forma:

 val incrementoMas2 = (x: Int) => x + 2
 println(s"[2] Apply[Option].ap(Some(3))(Some(incrementoMas2))=${Apply[Option].ap(Some(3))(Some(incrementoMas2))}")

La salida por consola es la siguiente:

 [2] Apply[Option].ap(Some(3))(Some(incrementoMas2))=Some(5)

Como he comentado antes, Scalaz contiene funciones de la sintaxis de Apply. Así, la función de la sintaxis de la función ap es el símbolo «<*>». El ejemplo anterior utilizando la sintaxis y utilizando una función que incrementa un entero en 3 unidades, es el siguiente:

 println(s"[3] 3.some <*> {(_: Int) + 3}.some=${3.some <*> {(_: Int) + 3}.some}")
 println(s"[3.1] 3.some <*> incrementoMas2.some=${3.some <*> incrementoMas2.some}")

La salida por consola es la siguiente:

 [3] 3.some <*> {(_: Int) + 3}.some=Some(6)
 [3.1] 3.some <*> incrementoMas2.some=Some(5)

Ejemplo Apply con dos functores

Dada una función con nombre sumaDosEntores la cual realiza la suma de dos enteros, se aplica la función ap2 de Apply para realizar la suma de dos functores definido por dos elementos Option de valores enteros de la siguiente forma:

 val sumaDosEnteros = (e1:Int, e2:Int) => e1 + e2
 val result2 = Apply[Option].ap2(Some(3), Some(4))(Some(sumaDosEnteros))
 println(s"[2] Apply[Option].ap2(Some(3), Some(4))(Some(sumaDosEnteros))=${result2}")

La salida por consola es la siguiente:

 [2] Apply[Option].ap2(Some(3), Some(4))(Some(sumaDosEnteros))=Some(7)

Aplicando la función «<*>» de la sintaxis de Apply, el ejemplo anterior, se define de la siguiente forma:

 println(s"[3.2] 3.some + 4.some <*> sumaDosEnteros.some=${ 3.some <*> {4.some <*> {sumaDosEnteros.curried.some}} }")

La salida por consola es la siguiente:

 [3.2] 3.some + 4.some <*> sumaDosEnteros.some=Some(7)

La sintaxis contiene otras funciones, como por ejemplo:

  •  función <*, dado dos functores se selecciona el functor de la izquierda
 println(s"1.some <* 2.some=${1.some <* 2.some}")
 println(s"none <* 2.some=${none <* 2.some}")

La salida por consola es la siguiente:

 1.some <* 2.some=Some(1)
 none <* 2.some=None
  • función *>, dado dos functores, se selecciona el functor de la derecha.
 println(s"1.some *> 2.some=${1.some *> 2.some}")
 println(s"1.some *> none=${1.some *> none}")

La salida por consola es la siguiente:

 1.some *> 2.some=Some(2)
 1.some *> none=None

Applicative

El API Applicative permite mapear una función con N parámetros. El API Applicative hereda del API Apply con lo cual el API opera con functores. Las funciones principales del API es la función point y su alias pure, las cuales permiten definir un functor a partir de un elemento de un determinado tipo. La definición de las funciones son las siguientes:

trait Applicative[F[_]] extends Apply[F] { self =>
 def point[A](a: => A): F[A]
 def pure[A](a: => A): F[A] = point(a)
 [...]
}

Ejemplos básicos Applicative

Dado un número entero, se aplica la función point del API Applicative para crear un functor Option y List de la siguiente forma:

 val result4 = Applicative[Option].point(3)
 println(s"[0] Applicative[Option].point(3)=${result4}")
 val result5 = Applicative[List].point(3)
 println(s"[0_1] Applicative[List].point(3)=${result5}")

La salida por consola es la siguiente:

[0] Applicative[Option].point(3)=Some(3)
[0_1] Applicative[List].point(3)=List(3)

Composición de funciones con Applicative

Dado una función que realiza el incremento de 10 unidades de un elemento entero, se define un elemento Applicative del tipo List de la siguiente forma:

 val result6 = Applicative[List].point(3).map( (elem:Int) => elem + 10 )
 println(s"[0_2] Applicative[List].point(3)=${result6}")

La salida por consola es la siguiente:

[0_2] Applicative[List].point(3)=List(13)

Función ap y apX en Applicative

  • Dada una función de incremento de un entero en dos unidades y un functor representado por un Option de enteros, se aplica la función ap del API Applicative de la siguiente forma:
 val inc: Int => Int = (x: Int) => x + 2
 val result1 = Applicative[Option].ap(Some(1))(Some(inc))
 println(s"[1] inc = (x: Int) => x + 2")
 println(s"[1] Applicative[Option].ap(Some(1))(Some(inc))=${result1}")

La salida por consola es la siguiente:

 [1] inc = (x: Int) => x + 2
 [1] Applicative[Option].ap(Some(1))(Some(inc))=Some(3)
  • Dada una función que transforma un entero en un functor del tipo Option; dada una función que realiza la operación de incremento de un elemento Option de enteros en dos unidades, se aplica la función ap del API Applicative de la siguiente forma:
 val inc: Int => Int = (x: Int) => x + 2
 val enteroASome: Int => Some[Int] = (x:Int) => Some(x)
 val optionDeFuncionDeEntero: Some[Int => Int] = Some(inc)
 val result1_1 = Applicative[Option].ap(enteroASome(1))( optionDeFuncionDeEntero )
 println(s"[1_1] Applicative[Option].ap(enteroASome(1))( optionDeFuncionDeEntero )=${result1_1}")

La salida por consola es la siguiente:

[1_1] Applicative[Option].ap(enteroASome(1))( optionDeFuncionDeEntero )=Some(3)
  • Dado un functor del tipo Option de enteros y ninguna función, el resultado de aplicar la función ap del API Applicative es el siguiente:
 val result1_2 = Applicative[Option].ap(Some(1))(None)
 println(s"[1_2] Applicative[Option].ap(Some(1))(None)=${result1_2}")

La salida por consola es la siguiente:

[1_2] Applicative[Option].ap(Some(1))(None)=None
  • Dada una función que realiza la suma de tres elementos enteros, se aplica la función ap3 del API Applicative de tres functores definidos en tres elementos Option de enteros de la siguiente forma:
 val sumaDe3 = (a: Int, b: Int, c: Int) => a + b + c
 val result2 = Applicative[Option].ap3(Some(1), Some(2), Some(3))(Some(sumaDe3))
 println(s"[1_3] Applicative[Option].ap3( Some(1), Some(2), Some(3) )(Some(sumaDe3))=${result2}")

La salida por consola es la siguiente:

[1_3] Applicative[Option].ap3( Some(1), Some(2), Some(3) )(Some(sumaDe3))=Some(6)
  • Dada una función que realiza la suma de tres elementos enteros, se aplica la función ap3 del API Applicative de tres functores definidos en tres elementos Option de enteros, uno de los cuales con valor None, de la forma siguiente:
 val sumaDe3 = (a: Int, b: Int, c: Int) => a + b + c
 val result2_1 = Applicative[Option].ap3(Some(1), None, Some(3))(Some(sumaDe3))
 println(s"[1_4] Applicative[Option].ap3( Some(1), None, Some(3) )(Some(sumaDe3))=${result2_1}")
 println

La salida por consola es la siguiente:

[1_4] Applicative[Option].ap3( Some(1), None, Some(3) )(Some(sumaDe3))=None
  • Dada una función que realiza la suma de tres elementos enteros, se aplica la función ap3 del API Applicative de tres functores definidos en tres elementos List de enteros de la siguiente forma:
 val sumaDe3 = (a: Int, b: Int, c: Int) => a + b + c
 val result3 = Applicative[List].ap3(List(1), List(2), List(3))(List(sumaDe3))
 println(s"[1_5] Applicative[List].ap3( List(1), List(2), List(3) )(List(sumaDe3))=${result3}")
 println

La salida por consola es la siguiente:

[1_5] Applicative[List].ap3( List(1), List(2), List(3) )(List(sumaDe3))=List(6)

Función Lift

  • Dada una función que realiza la suma de tres elementos de enteros, se define una función liftSumaDe3 a partir de la función de sumas de tres enteros para realizar la suma de tres elementos del tipo definido en el Applicative. El snippet es el siguiente:
 val sumaDe3 = (a: Int, b: Int, c: Int) => a + b + c
 val liftSumaDe3 = Applicative[Option].lift3( sumaDe3 )
 println(s"[1_6] liftSumaDe3( Some(2), Some(3), Some(5) )=${liftSumaDe3( Some(2), Some(3), Some(5) )}")
 println

La salida por consola es la siguiente:

[1_6] liftSumaDe3( Some(2), Some(3), Some(5) )=Some(10)

Función sequence

  • La función sequence realiza la conversión de tipos del elemento pasado por parámetro, es decir, dado un elemento P[G[_]], lo convierte en G[P[_]]. El snippet ejemplo es el siguiente:
 val resultApplicativeSequence = Applicative[Option].sequence(List(some(1), some(2), some(3)))
 println(s"[1_7] Applicative[Option].sequence(List(some(1), some(2), some(3)))=${Applicative[Option].sequence(List(some(1), some(2), some(3)))}")
 println

La salida por consola es la siguiente:

[1_7] Applicative[Option].sequence(List(some(1), some(2), some(3)))=Some(List(1, 2, 3))

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

Scalaz II: type class básicos

En la preente entrada, Scalaz II: Básico, me centraré en las types classes de Scalaz con un funcionamiento básico; como son las type classes: Equal, Show y Enum.

Scalaz Equal

La type classes Equal define funciones para realizar comparaciones. Para utilizar el API Equal, hay que importar el API y la sintaxis de Scalaz de la siguiente forma:

 import scalaz.Equal
 import scalaz.Scalaz._

Tipos básicos

Unos ejemplos de comparaciones con tipos básicos son los siguientes:

 println(s"1 === 1=>${1 === 1}")
 println(s"1 === 'algo'=>${ 1 == "algo" }")
 println(s"1.some =/= 2.some=>${1.some =/= 2.some}")
 println(s"1 assert_=== 1=>${1 assert_=== 1}")

La salida por consola del snippet anterior es el siguiente:

1 === 1=>true
1 === 'algo'=>false
1.some =/= 2.some=>true
1 assert_=== 1=>()

Ejemplos con case class

Sea una case class para definir una entidad de negocio cualquiera y unas instancias de dicha case class definidas de la siguiente forma:

 case class LuzDeTrafico(nobre: String)
 val rojo = LuzDeTrafico("rojo")
 val amarillo = LuzDeTrafico("amarillo")
 val verde = LuzDeTrafico("verde")

Sea una referencia implícita que define una operación con el API Equal con la case class anterior:

implicit val TrafficLightEqual: Equal[LuzDeTrafico] = Equal.equal(_ == _)

Se puede definir operaciones de comparación de la siguiente forma:

 println(s"rojo === rojo?${rojo === rojo}")
 println(s"rojo === amarillo?${rojo === amarillo}")

La salida por consola del snippet anterior es el siguiente:

 rojo === rojo?true
 rojo === amarillo?false

Scalaz Show

La type classes Show permite definir estructuras de datos como String. Los siguientes ejemplos muestran las funciones del API Show:

 println(s"3.show=>${3.show}")
 println(s"3.shows=>${3.shows}")
 "Saludos a la consola".println
 println(s"Saludos a la consola con show=>${ "Saludos a la consola con show".show}")

La salida por consola del snippet anterior es el siguiente:

 3.show=>3
 3.shows=>3
 "Saludos a la consola"
 Saludos a la consola con show=>"Saludos a la consola con show"

Scalaz Enum

La type class Enum permite definir estructuras secuenciales ordenadas. Las estructuras que podemos definir son las siguientes: lista de enteros, Streams de enteros y lista de caracteres. Unos snippet de ejemplo son los siguientes:

  • Definición de un rango desde los caracteres ‘a’ hasta ‘e’
 val enum1 = 'a' to 'e'
 println(s"'a' to 'e'=>${enum1}")
 println("Impresión del rango de enum1")
 enum1.foreach(println(_))
 println

La salida por la consola es la siguiente:

 'a' to 'e'=>NumericRange a to e
 Impresión del rango de enum1
 a
 b
 c
 d
 e
  • Definición de una lista con los elementos desde el caracter ‘a’ hasta ‘e’ y una lista de enteros desde 1 hasta 20.
 val enum2 = 'a' |-> 'e'
 println(s"enum2=>${enum2}")
 val enum2_1 = 1 |-> 20
 println(s"enum2_1=>${enum2_1}")

La salida por la consola es la siguiente:

 enum2=>List(a, b, c, d, e)
 enum2_1=>List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
  • Definición de un Stream de enteros con los elementos comprendidos desde el valor 3 hasta 5.
 val enum3 = 3 |=> 5
 println(s"emun3=>${enum3}")
 println(s"emun3=>${enum3.headOption}")

La salida por la consola es la siguiente:

 emun3=>scalaz.EphemeralStream$$anon$5@37e547da
 emun3=>Some(3)

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

Scalaz I: Functores y funciones como functores

Scalaz es una librería de programación funcional para el lenguaje Scala. Scalaz proporciona un conjunto de estructuras de datos para completar la biblioteca Scala; todas estas estructuras, están definidas sobre type classes y sus correspondientes instancias.

En la presente entrada, Scalaz I: Functores y funciones como functores, me centraré en los functores y en definir funciones como functores mediante los elementos de la librería Scalaz.

Definición de función curring

Las funciones curring es aquella técnica funcional nombrada en honor al matemática Haskell Curry el cual fue el creador del lenguaje funcional Haskell.

Una función curry es aquella función que define un conjunto de parámetros y, en las invocaciones a dicha función, pueden emplearse un número menor de parámetros. Así, podemos definir un conjunto de funciones a partir de una función base. Un ejemplo de función curry es la siguiente:

 def funcionBase(x:Int)(y:Int)= x + y
 val funcionCurry1 = funcionBase(2)(_)
 println(s"funcionCurry1(3)=${funcionCurry1(3)}")

La función base con técnica curring es la función funcionBase la cual define una lista de parámetros separados con paréntesis. A partir de la función base, definimos la función funcionCurry1 cuyo primer parámetro tiene el valor fijo 2 y, el segundo parámetro, un valor a determinar. Una vez definida la funcionCurry, se realiza la invocación de la función con el valor variable.

La salida por consola del ejemplo anterior es la siguiente:

 funcionCurry1(3)=5

La función funcionCurry1 tiene como primer parámetro el valor enteror 2 y, si quisiéramos, podríamos definir tantas funciones como valores enteros como primer parámetro.

Definición de Functor

La teoría de categorías es aquel estudio de las matemáticas que trata mediante axiomas estructuras abstractas como si fueran una, utilizando objetos y morfismos. Los objetos y los morfirmos forman una categoría. Así, una categoría es un conjunto de objetos y unos morfismos los cuales son las transformaciones entre dichos objetos.

Dada una categoría A, un functor es aquel morfismo que transforma la categoría A en otro categoría B. A nivel práctico, desde un punto de vista de un desarrollador, un functor se puede ver como aquella operación que transforma un elemento A en otro B y, a un bajo nivel en el lenguaje Scala, se corresponde con la función map.

Funciones como curry

En el apartado anterior, Definición de función curring, he definido una función de tipo curry y, en el presente apartado, describiré cómo se transforma una función como tipo curry. Para transformar una función como curry hay que aplicar la función curried.

En el siguiente ejemplo, se define una función curry con nombre lista1; se define una lista de enteros a la cual se aplica una función para realizar la multiplicación de los elementos por un número; y, una vez definida, se realiza la invocación de la función por el operador a multiplicar. Como se observa en la segunda línea del siguiente ejemplo, se aplica la función map con un valor entero fijo correspondiente con el segundo parámetro de la función de lista1.

 val lista1 = List(1, 2, 3, 4) map { (_: Int) * (_: Int) }.curried
 val resultadoLista1 = lista1.map {_ (9)}
 println(s"Resultado1=${resultadoLista1}")
 println

La salida por consola del ejemplo anterior es la siguiente:

 Resultado1=List(9, 18, 27, 36)

Funciones como functores

En el apartado anterior con nombre Definición de Functor, he realiza una descripción a alto nivel de un functor. En el presente apartado, describiré cómo definir functores con funciones.

Las importaciones de los elementos necesarios de Scalaz para los ejemplos siguientes, son los siguientes:

 import scala.language.higherKinds
 import scalaz.std.list._
 import scalaz.std.option._
 import scalaz.syntax.functor._
 import scalaz.{Functor, Monad}

La definición de un Functor con Scalaz de una lista de enteros y un Option de enteros es el siguiente:

 val list: List[Int] = Functor[List].map(List(1, 2, 3))(_ * 2)
 val option: Option[String] = Functor[Option].map(Some(123))(_.toString)
 println("List(1, 2, 3), [_ * 2] =>" + list)
 println("Some(123), _.toString =>" + option)

En el ejemplo anterior, defino un Functor de un tipo List y un segundo Functor de tipo Option. A estos dos functores, se aplican la función map para realizar la transformación, mediante una función, de una lista y de un Option. La función map del elemento scalaz.Functor tiene como parámetros los datos y la función que los modifica.

La salida por consola del ejemplo es la siguiente:

 List(1, 2, 3), [_ * 2] =>List(2, 4, 6)
 Some(123), _.toString =>Some(123)

En muchos casos, es necesario realizar la función de un tipo genérico sin conocer los datos; para ello, utilizamos la función lift. Así, como ejemplo, podemos definir un Functor para un Option de la siguiente manera:

 def lifted: (Option[Int]) => Option[Int] = Functor[Option].lift((x: Int) => x + 1)
 println("0.- lift((x: Int) => x + 1; Some(54) =>" + lifted(Some(54)))

La salida por consola es la siguiente:

 0.- lift((x: Int) => x + 1; Some(54) =>Some(55)

Si deseamos realizar alguna operación sobre el resultado, se puede aplicar la función map como sigue:

 println("0.1.- lift((x: Int) => x + 1; Some(54) =>" + lifted(Some(54)).map((x: Int) => x + 5))

La salida por consola es la siguiente:

 0.1.- lift((x: Int) => x + 1; Some(54) =>Some(60)

Los ejemplos descritos hasta el momento han partido de la definición de un elemento Functor; pero, si deseamos definir una función y utilizarla como un Functor o una Mónada, ¿cómo se realiza? Para ello, es necesario definir la función y emplear la función lift. Para el caso de una Mónada, un ejemplo es el siguiente:

 val func = ((x: Int) => x + 1) lift Monad[Option]
 println("1 ->" + func(Some(87)))

La salida por consola es la siguiente:

 1 ->Some(88)

Si deseamos aplicar mas transformaciones con la función map de la Mónada, se realiza de la siguiente forma:

 println("1.1 ->" + func(Some(87)).map((x: Int) => x + 1))
 println("1.2 ->" + func(Some(87)).map((x: Int) => x + 1).map((x: Int) => x + 10))

La salida por consola es la siguiente:

 1.1 ->Some(89)
 1.2 ->Some(99)

Si deseamos definir la transformación como una función, se puede realizar de la siguiente forma:

 def func2(elem: Int) = func(Some(elem)).map((x: Int) => x + 1).map((x: Int) => x + 10)
 println("1.3 ->" + func2(87))

La salida por consola es la siguiente:

 1.3 ->Some(99)

La definición de una función para realizar un morfismo de una lista de enteros en otra lista de enteros, se puede definir de la siguiente manera.

 def liftedListInt: (List[Int] => List[Int]) = Functor[List].lift((x: Int) => x * 5)
 println("2 ->" + liftedListInt(List(1, 2, 3)))
 println("2.1 ->" + liftedListInt(List(1, 2, 3)).map((x: Int) => x * 5))

La salida por consola es la siguiente:

 2 ->List(5, 10, 15)
 2.1 ->List(25, 50, 75)

Otras funciones

Scalaz es un conjunto de componentes de type classes y, el patrón type classes, permite la definición de sintaxis. En el presente apartado, se identifican un conjunto de funciones pertenecientes a la sintaxis de functores. La importación de los elementos necesarios para los ejemplos son las siguientes:

 import scalaz.Functor
 import scalaz.Scalaz._

La selección de las funciones que he realizado son las siguientes:

  • Para convertir todos los elementos de una estructura a un elemento determinado, se emplea la función >|. Un ejemplo es el siguiente:
 val listaX = List(1,2,3) >| "x"
 println(s"List(1,2,3) >| 'x'=${listaX}")

La salida por consola es la siguiente:

 List(1,2,3) >| 'x'=List(x, x, x)
  • Para convertir todos los elementos de una estructura a un elemento determinado, se emplea la función as. Un ejemplo es el siguiente:
 val listaAs = List(1,2,3) as "x"
 println(s"List(1,2,3) as 'x'=${listaAs}")

La salida por consola es la siguiente:

 List(1,2,3) as 'x'=List(x, x, x)
  • Para convertir un conjunto de entrada en un conjunto de tuplas con elementos duplicados, se emplea la función fpair. Un ejemplo es el siguiente:
 val listaFPair = List(1,2,3).fpair
 println(s"List(1,2,3).fpair=${listaFPair}")

La salida por consola es la siguiente:

 List(1,2,3).fpair=List((1,1), (2,2), (3,3))
  • Para convertir un conjunto de entrada en un conjunto de duplas cuyo elemento derecho sea uno determinada, se emplea la función strengthR. Un ejemplo es el siguiente:
 val listaStrengthR = List(1,2,3).strengthR("x")
 println(s"List(1,2,3).strengthR('x')=${listaStrengthR}")

La salida por consola es la siguiente:

 List(1,2,3).strengthR('x')=List((1,x), (2,x), (3,x))
  • Para convertir un conjunto de entrada en un conjunto de duplas cuyo elemento izquierdo sea uno determinada, se emplea la función strengthL. Un ejemplo es el siguiente:
 val listaStrengthL = List(1,2,3).strengthL("x")
 println(s"List(1,2,3).strengthL('x')=${listaStrengthL}")

La salida por consola es la siguiente:

 List(1,2,3).strengthL('x')=List((x,1), (x,2), (x,3))
  • Para convertir un conjunto de entrada en un conjunto de tuplas vacías, se emplea la función void. Un ejemplo es el siguiente:
 val listaVoid = List(1,2,3).void
 println(s"List(1,2,3).void=${listaVoid}")

La salida por consola es la siguiente:

 List(1,2,3).void=List((), (), ())

La definición de un Functor o una Mónada mediante el patrón funcional type classes es una operación que requiere, para el desarrollador principiante en la programación funcional, un entendimiento de conceptos complejos. Mediante la utilización de Scalaz utilizar Functores y definir funciones como functores es un proceso comprensible y fácil de entender.

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