spinner

¿Qué es el patrón Type Class y cómo usarlo?

Patrones: ¿para qué sirven?

En el ámbito de la informática y en otras disciplinas los patrones son importantes. En informática, un patrón es una técnica que permite la resolución de problemas de distinta índole, como por ejemplo, problemas de comportamiento, problemas estructurales, o bien, problemas creacionales. Uno de los patrones clave es el patrón Type Class, que vamos a explorar en este post.

Type Class permite la construcción de un sistema de tipos que soporta «polimorfismo paramétrico», o diferentes formas de parámetros. Este patrón está compuesto por unos elementos básicos: una interfaz con el que definimos un API, unas instancias del interfaz y unos interfaces de tipo. Además, ampliando el patrón, podemos definir un lenguaje y, en función del componente lógico, unas leyes que cumplen la funcionalidad.

Type Class tiene su origen en la programación funcional y, en concreto, su primera aparición es en lenguaje Haskell. Eso sí, los ejemplos que enseñaré en este post no los realizaré en Haskell, sino en lenguaje Scala. ¡Vamos allá!

Un poco de Scala

Vamos a ponernos un poco en contexto. El lenguaje Scala es creado por Martin Odersky en mayo de 2011. Es multiparadigma, orientado a objetos y es funcional. Desde el punto de vista sintáctico, Scala es un lenguaje parecido a Java. Es verdad, con Scala puede programar como Java, pero si lo haces ¡no sacarás el verdadero potencial que te brinda la programación funcional!

¿Y por qué? El principal problema es la fase de iniciación, pues la curva de aprendizaje es lenta y no es tan sencilla como aprender un lenguaje con un paradigma que se conoce. No obstante, cuando rompes y comprendes la primera barrera quedas maravillado del potencial del paradigma y del lenguaje.

En los siguientes apartados, intentaré mostrarte pequeñas píldoras que te permitirán comprender y, sobre todo, que te animen y te quiten los miedos a la programación funcional. ¡Espero conseguirlo!

Scala: con más detalle

 Vamos a ver de forma breve ciertos elementos del lenguaje y conceptos de programación:

  1. Trait: La palabra «trait» permite definir un interfaz.
  2. Class: La palabra «class» permite definir una clase.
  3. Object: un objeto Singleton de una clase. El compilador realiza automáticamente la creación de la clase y la instancia de la clase singleton.
  4. Herencia: La herencia se define con la palabra «extends» y la implementación de «trait» mediante la palabra «with».
  5. Val: El lenguaje Scala es un lenguaje inmutable, es decir, permite definir objetos o atributos de clase con los cuáles, una vez creados, no pueden ser cambiados. Con la palabra «val», se permite definir atributos inmutables.
  6. Var: El lenguaje Scala, a pesar de ser inmutable, permite la definición de valores mutables. Con la palabra «var», se permite definir atributos mutables.
  7. Implicit: El lenguaje Scala permite definir elementos implícitos. Los elementos implícitos son elementos que, una vez creados, se pueden referenciar en otras partes del código de forma implícita. El compilador se encargará de referenciarlos.

Una vez vistos estos elementos, vamos a ver un poco en qué consiste la programación funcional.

Programación funcional

En la programación funcional se trabaja, claro está, con funciones. Estas reciben un determinado número de parámetros y retorna un valor, objeto o tupla. Un ejemplo básico es la definición de una función que tiene como parámetros dos elementos de un tipo y su resultado es un elemento del mismo tipo.

Vamos a explicarlo de manera clara. Desde un punto de vista matemático, la función se puede definir de la siguiente forma: (A, A) => A; siendo la descripción la siguiente:

  • A es un tipo determinado: parte izquierda (A,A) es la definición de una función que recibe dos parámetros del tipo A, y la parte derecha A, es el resultado de la función de tipo A. Esta definición la podemos identificar como «Semigrupo».

De la misma forma que definimos una función, podemos definir un valor vacío de un tipo. Es decir, si tratamos un tipo entero, un valor vacío puede ser el número cero.

Aquí, la unión de un semigrupo y un elemento vacío crea lo que se denomina un «monoide». La definición de un monoide en Scala es la siguiente:

monoide Scala

Una vez visto esto ya estamos más cerca de llegar a Type Class (¡bien!). El ejemplo que usaremos para describirlo es la implementación de un Monoide que implemente la operación lógica OR.

¡Ahora sí! Patrón type class

El patrón Type Class está compuesto de tres elementos: el tipo de clase (el cual define el interfaz), la API (las instancias de tipos definidos) y los métodos de interfaz. Vamos a verlos en detalle:

  • La definición del type class que forma el API del monoide de la operación suma es la siguiente

type class API monoide
En el anterior snippet, se define un «trait» con un tipo parametrizado de tipo A que hereda de Monoid

  • La definición de las instancias para un tipo parametrizado de tipo Boolean es esta
    instancias tipo parametrizado boolean

En el snippet que hemos visto arriba, se define un «trait» con los siguientes elementos: un objeto implícito monoidBooleanSuma de tipo MonoidSuma con un tipo parametrizado Boolean. Además, se define una función «apply» con la función de constructor de tipos implícitos de tipo MonoidSuma con un tipo parametrizado A.

En este módulo definimos las instancias del interfaz con un tipo determinado. Es la parte menos pura desde un punto de vista funcional ya que es la parte en donde se define las acciones y funciones no puras.

  • La definición de los métodos del interfaz es la siguiente

metodos interfaz Type Class

Aquí se define un objeto sintaxis en el trait MonoidSumaSyntax con dos funciones: la primera, la función «++++» que define una función que referencia la función combine de las instancias pasadas por parámetro. La segunda, la función «emptySuma» que define una función que referencia la función «empty» de las instancias pasada por parámetro.

  • Leyes del monoide

El monoide es un concepto de la programación funcional basado propiedades matemáticas. Para realizar los test, es necesario definir las reglas matemáticas que cumplen el monoide. Dichas propiedades son: propiedad asociativa y propiedad identidad. Así, en Scala las leyes del monoide las definimos de la siguiente forma:

definición monoide Scala

A los tres apartados anteriores les falta el elemento que los unifique en una unidad. Ese elemento es un objeto que se define de la siguiente manera:

objeto unidad monoide

También os dejo este sketch (hecho por mí, claro!) por si os sirve para haceros una idea de la arquitectura:

arquitectura Type Class

Fuente: elaboración propia

Test del type class

Bien. Vamos a ver cómo hacer los tests a este patrón, que será con la librería ScalaTest. La definición de la librería en sbt, gestor de ciclo de vida, es la siguiente:

libreria test type class

La definición del test para los ejemplos de los apartados anteriores es el siguiente:

definicion test

En el snippet anterior se define un test en donde se instancia las leyes del Type Class y se verifican.

Conclusiones

Hemos descrito el patrón Type Class explicando sus elementos y realizando la descripción del operador lógico OR. Además, de una forma muy genérica (dada la complejidad del tema), también hemos realizado una descripción de cómo definir un DSL. Para terminar, os he dejado algunas librerías genéricas por si pueden seros de utilidad.

Dado el nivel de abstracción del tema espero que este post os haya servido, al menos, para comprender de una forma genérica un patrón básico para definir una arquitectura funcional, y sobre todo, para eliminar aquellas reticencias o miedos que genera la programación funcional. ¡Animaos a usar el patrón Type Class!

Imagen de portada: Álvaro Monsalve

Las opiniones vertidas por el autor son enteramente suyas y no siempre representan la opinión de BBVA Next Technologies.

¿Quieres saber que más cosas hacemos en BBVA Next Technologies?