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:

 

AWS Redshift y Luigi, módulo Redshift: operación de consulta

En la presente entrada, AWS Redshift y Luigi, módulo Redshift: operación de consulta , realizaré la descripción de cómo se define una tarea para realizar una consulta o ejecución de una sentencia SQL sobre Amazon Redshift.

Para realizar las operaciones de consulta en Redshift es necesario la implementación de una clase que herede de la clase RedshiftQuery del módulo de Redshift de Luigi. La importación de la clase, se realiza de la siguiente forma:

from luigi.contrib.redshift import RedshiftQuery

La solución que propongo es una jerarquía de clases para poder definir varias consultas. La solución está compuesta por una clase que hereda de RedshiftQuery y la clase que ejecuta una consulta. La clase genérica de consultas con la carga de la configuración es la siguiente:

class AbstractRedshiftQuery(RedshiftQuery):
  """Clase genérica para la realización de consultas en Redshift"""
  config = RedshiftConnection()
  host = config.host
  database = config.database
  user = config.user
  password = config.password
  def output(self):
    return RedshiftTarget(
    host=self.host,
    database=self.database,
    user=self.user,
    password=self.password,
    table=self.table,
    update_id=self.update_id,
    port=5439)

En el snippet anterior, se define la clase AbstractRedshiftQuery que hereda de RedshiftQuery; y, además, se define la clase RedshiftConnection con los datos de configuración de la conexión a Redshift. Los datos de configuración son: host, database, user y password.

Una vez definida la clase AbstractRedshiftQuery, definimos la clase con la consulta. La clase con la sentencia SQL es la siguiente:

class CreateHistorico(AbstractRedshiftQuery):
  """
  La tarea CreateHistorico realiza la creación de una tabla.
  """
  table = "tabla_historia"
  query = """
    CREATE TABLE tabla_historia
    (
     sociedad varchar(60),
     fecha timestamp
    )
  """

En el snippet anterior, se define la clase CreateHistorico que realiza la creación de una tabla en Redshift la cual hereda de la clase AbstractRedshiftQuery. Para realizar la consulta, es necesario definir el atributo query con la sentencia a ejecutar y el atributo table. El resultado es la creación de la tabla tabla_historica en Redshift.

Para su ejecución, se realiza por los procedimientos normales de arranca de Luigi.

En este caso, se ha realizado la creación de una tabla pero, se puede definir cualquier otra sentencia: inserción, borrado,…

Para el lector interesado, las entradas publicadas hasta la fecha son las siguientes:

AWS S3 y Luigi, módulo S3: eliminación y copiado de carpetas

En la presente entrada, AWS S3 y Luigi, módulo S3: eliminación y copiado de carpetas, realizaré la descripción de la tareas para realizar con Luigi las operaciones de eliminación y copiado de carpetas de un bucket en  Amazon S3 con Luigi. Para realizar las operaciones sobre S3 de Amazon con Luigi, necesitamos instanciar un cliente S3Client dentro de una tarea.

En nuestro caso, definiré una jerarquía de clases en donde la clase padre define la referencia a un cliente S3 de Luigi: de esta forma, las clases hijos simplemente deben de pasar los parámetros con los datos necesarios. La importación del cliente es la siguiente:

from luigi.contrib.s3 import S3Client

La cabecera de la clase con la definición de la referencia al cliente es la siguiente:

class AbstractS3Client(luigi.Task):
  """
  La clase AbstractS3Client realiza la operaciones básicas en S3.
  """
  s3config = S3Configuration()
  s3Client = S3Client(profile_name=s3config.profileName, host=s3config.awsHostS3)
  [...]

Operación de eliminación

La operación de eliminación del contenido de una carpeta de S3 se realiza empleando la función remove del cliente S3Client. Además del path de la carpeta que se quiere eliminar, es necesario el nombre del Bucket de S3.

El snippet con el código con la funcionalidad de la eliminación de la carpeta es la siguiente:

def remove(self, bucketName, path):
  """
  El método removeProcess realiza la eliminación del contenido definido en un path pasado por parámetro de un
  bucket determinado.
  :param bucketName: Nombre del bucket
  :param path: Path
  :return:
  """
  path = "s3://{bucketName}/{path}/".format(bucketName=bucketName, path=path)
  if self.s3Client.exists(path):
    logging.info("\n[AbstractS3Client.removeProcess] Verificado el path={path}".format(path=path))
    resultRemove = self.s3Client.remove(path)
    if resultRemove:
      logging.info("[AbstractS3Client.removeProcess] Eliminación de {path}: OK".format(path=path))
    else:
      logging.info("[AbstractS3Client.removeProcess] Eliminación de {path}: KO".format(path=path))
  else:
    logging.info("[AbstractS3Client.removeProcess] NO Verificado el path={path}".format(path=path))

En el snippet anterior, código que se integra en la clase AbstractS3Client, define el path de la carpeta del bucket a eliminar con la variable path; se realiza la comprobación de existencia del bucket que se quiere eliminar con la función exists; si la comprobación anterior es correcta, se realiza la eliminación con la función remove, en otro caso, se muestra un mensaje informativo.

Operación de copia

La operación de copiado del contenido de una carpeta origen a otra carpeta destino, se realiza con la función copy del cliente S3Client. Además, del path de la carpeta origen y destino, es necesario el nombre del bucket de S3.

El snippet con el código con la funcionalidad de la operación de copiado de las carpetas, es el siguiente:

def copy(self, bucketName, sourceFolder, targetFolder):
  """
  El método copyProcess realiza el copiado del contenido de una carpeta origen(sourceFolder) a una carpeta
  destino (targetFolder) de un bucket determinado (bucketName).
  :param bucketName: Nombre del bucket
  :param sourceFolder: Carpeta origen
  :param targetFolder: Carpeta destino.
  :return:
  """
  sourceFile = "s3://{bucketName}/{sourceFolder}/".format(bucketName=bucketName, sourceFolder=sourceFolder)
  if self.s3Client.isdir(sourceFile):
    targetFile = "s3://{bucketName}/{targetFolder}/".format(bucketName=bucketName, targetFolder=targetFolder)
    if self.s3Client.isdir(targetFile):
      resultadoCopiado = self.s3Client.copy(sourceFile, targetFile)
      logging.info("[AbstractS3Client.copy] Copiado contenido DESDE:{sourceFile} A:{targetFile}".format(sourceFile=sourceFile, targetFile=targetFile))
      logging.info("[AbstractS3Client.copy] Numero de ficheros copiados={resultadoCopiado0}".format(resultadoCopiado0=resultadoCopiado[0]))
      logging.info("[AbstractS3Client.copy] Tamaño copiado={resultadoCopiado1}".format(resultadoCopiado1=resultadoCopiado[1]))
    else:
      logging.info("[AbstractS3Client.copy] {targetFile} no es un directorio.".format(targetFile=targetFile))
  else:
    logging.info("[AbstractS3Client.copy] {sourceFile} no es un directorio.".format(sourceFile=sourceFile))

En el snippet anterior, código que se integra en la clase AbstractS3Client, define el path de la carpeta origen de copiado definido en la variabla sourceFile; se realiza la comprobación de existencia del directorio con la función isDir; si el path del directorio es correcto, se calcula el path de la carpeta destino de copiado definido en la variable targetFile; si el path del directorio destino es correcto empleando la función isDir, se realiza el copiado del contenido de la carpeta origen en la destino; y, de todas las comprobaciones, se muestra el mensaje de error si no se cumple.

Instancia de clases

Para ejecutar la funcionalidad descrita en el apartado anterior, es necesario definir una clase que herede de la clase AbstractS3Client. Al ser esta clase una tarea de Luigi, es necesario la implementación del método run. Si se desea que la tarea esté enlazada con otras tareas, es necesario definir el método output.

El snippet de una clase que hereda de la clase AbstractS3Client para realizar la eliminación de una carpeta es la siguiente:

class DeleteS3(AbstractS3Client):
  def run(self):
    s3Configuration = S3Configuration()
    self.remove(s3Configuration.bucketName, s3Configuration.sourceFolder)

El snippet anterior, define la clase S3Configuration la cual define las siguientes propiedades: bucketName, nombre del bucket; sourceFolder, nombre de la carpeta a eliminar; y, además, define la invocación del método remove para ejecutar la eliminación.

El snippet de una clase que hereda de la clase AbstractS3Client para realizar el copiado del contenido de una carpeta es el siguiente:

class CopyS3(AbstractS3Client):
  def run(self):
    s3Configuration = S3Configuration()
    self.copyProcess(s3Configuration.bucketName, s3Configuration.sourceFolder, s3Configuration.targetFolder)

El snippet anterior, define la clase S3Configuration la cual define las siguientes propiedades: bucketName, nombre del bucket; sourceFolder, nombre de la carpeta origen a copiar; targetFolder, carpeta destino de copiado; y, además, define la invocación del método remove para ejecutar la eliminación.

Las operaciones con Amazon S3 son sencillas de realizar con Luigi. Lo que se tiene que tener claro son todos aquellos datos de configuración para la ejecución de las operaciones.

Para el lector interesado, las entrada publicadas hasta la fecha sobre Luigi 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: