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: