Marco Ordoñez

Varianza en Scala

En la programación orientada a objetos estamos acostumbrados a la herencia pero cuando hablamos de programación funcional hablamos de polimorfismo con tipos (generics) y hablamos también de covarianza, contravarianza e invarianza para establecer el grado de detalle en los genéricos.

Cuando hablamos de covarianza nos referimos a subtipos, en Scala se puede un tipo como covariante utilizando el símbolo "+".

class Universe[+T]{...}  

Cuando hablamos de contravarianza nos referimos a los súpertipos, que en Scala se pueden definir utilizando el símbolo "-".

class Universe[-T]{...}  

Cuando hablamos de invarianza nos referimos al tipo en si.

class Universe[T]{...}  

Si sabemos que Universe[T] es un tipo parametrizado y A y B son tipos, a continuación veremos sus relaciones.

//Covariante
Universe[A] <: Universe[B]

//Contravariante
Universe[A] >: Universe[B]

//Invariante
Universe[A] ó Universe[B]  

Recordemos el principio de sustitución de Barbara Liskov:

"Sea ϕ(x) una propiedad comprobable acerca de los objetos x de tipo T. Entonces ϕ(y) debe ser verdad para los objetos y del tipo S donde S, es un subtipo de T."

Que en términos simples quiere decir que todo lo que podemos hacer con un tipo T también lo podremos hacer con su subtipo S.

Ahora veamos un ejemplo sobre el principio de Liskov.

trait Int2  
class Positive extends Int2  
class Negative extends Int2

type A = Int2 => Positive  
type B = Negative => Int2  

El tipo B recibe un tipo Negative y retorna un Int2, todo lo que podemos hacer es pasar un Negative y obtener un Int2. El tipo A recibe un tipo Int2 (que puede ser un Negative) y devuelve un Positive (que es un Int2), lo que quiere decir que el tipo A satisface el mismo contrato que el tipo B y podemos decir que A <: B.

Debemos conocer que la regla general para realizar subtipos en tipos de funciones es:

Siendo el tipo S
A1 => B1

Y el tipo T
A2 => B2

El tipo es S es un subtipo del tipo T siempre y cuando A2 sea un subtipo de A1 y B1 sea un subtipo de B2.

Esto se puede comprobar en nuestro ejemplo viendo que Negative(A2) es un subtipo de Int2(A1) y Positive(B1) es un subtipo de Int2(B2).

A1 => B1 <: A2 => B2  

Basado en lo que hemos visto, podemos decir que las funciones son contravariantes en su tipo de argumento y covariante en su tipo de resultado. El trait de Scala que define la función de 1 argumento es el siguiente:

trait Function1[-T, +T]{  
  def apply(x: T):U
}
Marco Ordonez