¿Qué son los Traits de Scala?


Muchas veces, al comenzar a trabajar en Scala nos encontramos con los razgos o "traits", que desafortunadamente tienden a confundir a los que se inician en Scala. Veremos que en realidad, los traits no solamente son un tema simple y sencillo, sino que además, proveen una gran cantidad de facilidades que nos permitirán programar mejor.


Este post lo estaré haciendo cargado de ejemplos, pues creo que es la mejor forma de explicar los traits. Además, originalmente esto fue publicado en quora. Pueden ver mi respuesta original (en inglés) aquí.

Ejemplo sencillo

En resumen, los traits de Scala son interfaces de Java que pueden tener funciones implementadas. Ahora, los traits de scala son una característica realmente poderosa del lenguaje y hay varias tareas interesantes que se pueden hacer con ellos. Primero, veamos su uso más básico: como interfaces de Java.


Evidentemente, esto es un ejemplo muy simple, pero sirve para ilustrar la forma en que se puede definir un "base trait Color" que tiene 2 métodos: "nombre" que no tiene implementación y "toString", el cual depende de la implementación del método "nombre". Los otros 3 traits son simples especializaciones del trait base "Color" y simplemente definen la función "nombre".

En este ejemplo, se puede apreciar que si llamamos al método "toString" de la clase MiRojo, vamos a obtener el valor "Color Rojo". Por supuesto, podemos extender directamente del trait "Color" en una clase, y lo único que necesitarímos sería definir el método "nombre" o hacer la clase abstracta.
En este punto, podemos ver que los traits son bastante similares a las interfaces en Java, pero al tener la capacidad de poder definir funcionalidad en los traits los convierten fácilmente en un artefacto mucho más poderoso.
Scala llama a esto "Interfaces ricas" (contrario a las "interfaces delgadas" de Java). Veamos otro ejemplo:

Interfaces ricas


Y como último ejemplo de como los traits se parecen a las interfaces en Java, se puede extender una clase con múltiples traits:
Al ver que los traits nos permiten definir comportamiento, y que podemos extender de múltiples traits al mismo tiempo, ¡parece que de inmediato se encendieran todas las alarmas de herencia múltiple!, por que de hecho, los traits son una forma de herencia múltiple.
Para nuestra suerte, Scala tiene una elegante forma de resolver el "Problema del diamante". Pero antes de pasar a este tema, veamos un último ejemplo de composición (que nos servirá luego para explicar la solución al problema del diamante).

Ejemplo de Interfaces ricas

Como primer paso, definimos una típica clase abstracta "OperacionInt" que lo único que (por el momento) es definir una operación "op" que retorna como resultado el mismo número de entrada.

Con esta clase abstracta, definamos entonces dos traits que los usaremos como "mixins".

Aunque de momento parece un poco extraño la sintaxis de estos dos traits, todo tendrá más sentido cuando veamos la salida del programa. De momento, aprovechemos para definir una clase concreta que implemente nuesta "OperacionInt".

Veamos entonces, una salida del sistema:

Podemos ver que instanciar una clase "MiOP" y el resultado de su operación "op" son los esperados. ¿Qué pasará si mezclamos el trait "Doblado" en la ecuación?:

Ahora sí podemos ver como el trait "Doblado" ha modificado radicalmente el comportamiento de nuestra operación "op". Debido a que este "artefacto" es bastante común, en Scala es posible mezclar (mix in) los traits a la hora del instanciamiento del objeto. Con esto, no es necesario estar creando definiciones de clases que implementen los traits:

Si llamásemos a "mezclado.op(5)", obtendríamos como resultado "10". Hemos entonces, modificado el comportamiento de nuestros objetos en tiempo de instanciación.

El problema del diamante

Todo esto resulta muy bien pero ¿qué pasa si intentamos mezclar los traits al mismo tiempo? Recordemos que tanto "Doblado" como "Incrementado" modifican por cuenta propia el comportamiento de la operación "op" de la clase "OperacionInt". Hagamos la prueba:

Analicemos un poco el comportamiento que hemos obtenido. Primero (y esta es la clave de como Scala resuelve el problema del diamante), podemos concluir que el comportamiento de "Incrementado" se resuelve primero
5 + 1 = 6
Y luego, el comportamiento de "Doblado" resuelve:
6 * 2 = 12

Tratemos ahora, de realizar la extensión pero con diferente orden:

Ahora, el comportamiento de Doblado se resuelve primero:
5 * 2 = 10
Y finalmente, el comportamiento de Incrementado:
10 + 1 = 11

En este punto, es evidente que Scala resuelve el problema del diamante de una forma lineal y dependiente del orden (de derecha a izquierda) con que sean extendidas las clases con los traits. El trait que sea extendido de último, será el primero en resolver y modificar el comportamiento.

Conclusión

Los traits en Scala son en definitiva, una de las herramientas más poderosas que posee el lenguaje en cuanto a orientación a objetos. Nos permite, no solamente reducir el tamaño del código de forma considerable, sino que además nos provee de formas de modificar comportamientos para casos donde sea necesario.

Entradas populares de este blog

Cómo leer una firma digital con C#:

Enviar Tweets A Través De La Consola, Usando Python.

Un emprendimiento móvil