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: