Los tipos de datos algebraicos es un concepto muy relacionado con la programación funcional.
En este artículo voy a hacer una introdución a estos conceptos y en futuros artículos profundizaré un poco más.
Leguajes modernos como Kotlin y Swift, aun siendo lenguajes orientados a objetos, incorporan soporte directo para representar los tipos de datos algebraicos Sum, que es algo que no ocurre en lenguajes orientados a objetos más tradicionales como Java o C#.
Por lo tanto, conocer en que consisten los tipos de datos algebraicos, conocer los tipos que existen, nos proporciona más herramientas para poder diseñar mejor nuestro código, aunque no trabajemos con lenguajes funcionales.
Como ya he comentado en algún artículo anterior, me gusta utilizar diferentes tintes de programación funcional donde creo que me pueden ayudar.
Tipo de datos Algebraicos (TDAs)
Un tipo de dato algebraico es la forma de expresar cualquier tipo de dato que solemos tener en una aplicación en base a álgebra.
Los tipos de datos algebraicos son tipos compuestos, es decir, un tipo formado por la combinación de otros tipos y se clasifican comunmente en dos: sumas y productos.
Por ejemplo:
Currency = USD + EUR + GBP
Money = Amount * Currency
La forma de leer esto es traducir sumas por OR y productos por AND.
Entonces podríamos decir que la moneda es Dollar o Euro o Libra y que el dinero se compone de una cantidad y una moneda.
Product
Un producto es el tipo que normalmente se puede crear en cualquier lenguaje de programación, ya sea funcional o no, como clases en Kotlin, Java, C#, Struct en Swift o C# etc..
Las partes de las que se componen se leen con AND.
Money = Amount * Currency
Se conocen como producto porque el número de posibles valores que puede tener es el producto del número de posibles valores de las partes que lo componen.
Vamos a ver un ejemplo utilizando Kotlin:
class Example(value1: Boolean, value2: Boolean)
La clase Example tiene dos tipos Boolean (value1, value2) y el tipo Boolean puede tener dos posibles valores true o false. Entonces si multiplicamos 2 X 2 es 4 y son los posibles valores.
Se refleja muy bien viendo las opciones que tenemos para crear una instancia de Example:
val example1 = Example(value1 = true, value2 = true)
val example2 = Example(value1 = false, value2 = false)
val example3 = Example(value1 = false, value2 = true)
val example4 = Example(value1 = true, value2 = false)
Vamos a variar el ejemplo:
class Example(value1: Boolean, value2: Byte)
La clase Example ahora tiene un tipo Boolean y un tipo Byte. El tipo Boolean puede tener dos posibles valores true o false y el tipo Byte puede tener 256 posibles valores. Entonces si multiplicamos 2 X 256 es 512 y son los posibles valores.
No vamos a escribir todas las formas de instanciar la clase Example con este cambio, pero veamos una pequeña representación:
val example1 = Example(value1 = true, value2 = -128)
val example2 = Example(value1 = true, value2 = -127)
.
.
val example255 = Example(value1 = true, value2 = 126)
val example256 = Example(value1 = true, value2 = 127)
val example257 = Example(value1 = false, value2 = -128)
val example258 = Example(value1 = false, value2 = -127)
.
.
val example511 = Example(value1 = false, value2 = 126)
val example512 = Example(value1 = false, value2 = 127)
Sum
Una tipo sum es un tipo de dato algebraico, también conocido como union discriminada o union disjunta, que tradicionalmente tenía solo soporte directo en lenguajes como Scala o Haskell.
En lenguajes como C# o Java algo similar se puede representar mediente Enum, pero los Enum tienen una serie de limitaciones en estos lenguajes que no hacen viable utilizarlos para representar tipos sum.
Lenguajes orientados a objetos modernos como Kotlin o Swift han incorporado soporte directo para definir tipo de datos sum.
Las partes de las que se compone un tipo sum se leen como OR porque el valor del objeto resultado solo puede contener una de las opciones.
Currency = USD + EUR + GBP
En este caso el valor de currency solo puede ser USD o EUR o GBP.
Se conocen como suma porque el número de posibles valores que puede tener es: la suma del número de posibles valores de las partes que lo componen.
Veamos un ejemplo utilizando Kotlin:
sealed class Example{
data class value1(val value: Boolean)
data class value2(val value: Boolean)
}
La clase Example tiene dos opciones de tipo Boolean, class value1 y class value2. Además el tipo Boolean puede tener dos posibles valores true o false. Entonces si sumamos 2 + 2 es 4 y son los posibles valores.
Veamos de nuevo como se crearían las posibles instancias de Example:
val example1 = Example.value1(true)
val example2 = Example.value1(false)
val example3 = Example.value2(true)
val example4 = Example.value2(false)
Hasta este momento, si Example se compone de dos tipos Boolean da igual si lo diseñamos como sum o como product, ya que el resultado de posibles combinaciones en ambos casos es 4.
Pero veamos que pasa si volvemos a poner uno de los tipos hijos como Byte cuando se diseña como sum:
sealed class Example{
data class value1(val value: Boolean)
data class value2(val value: Byte)
}
Ahora la clase Example tiene dos opciones, una de tipo Boolean y otra de tipo Byte. El tipo Boolean puede tener dos posibles valores true o false y el tipo Byte puede tener 256 posibles valores. Entonces si sumamos 2 + 256 es 258 y son los posibles valores.
Veamos como se instanciaria Example:
val example1 = Example.value1(true)
val example2 = Example.value1(false)
val example3 = Example.value2(-128)
val example4 = Example.value2(-127)
.
.
val example257 = Example.value2(126)
val example258 = Example.value2(127)
Las distintas combinaciones posibles varian bastante si se diñena como sum o como producto un tipo de dato.
Conclusiones
Hemos visto en este artículo una introdución a los tipos de datos algebraicos.
Utilizando distintos ejemplos sencillos ha quedado claro como se reduce el número de posibles combinaciones cuando diseñamos un tipo como sum, esto puede ser una gran ventaja a la hora de diseñar nuestros tipos.
Los que hemos utilizado durante mucho tiempo lenguajes tradicionales orientados a objetos, estamos acostumbrados a diseñar los objetos como productos y existen diferentes situaciones donde un tipo sum nos puede facilitar mucho la vida.
Pero ademas los tipos Sum pueden sacar ventaja del compilador y hacen nuestro código más seguro.
En el siguiente artículo profundizare más en los tipo sum.