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:

 

Scala V: introducción a Mónadas

En la presente entrada, Scala 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: