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:
Me gusta esto:
Me gusta Cargando...