Tipos existenciales ¿A qué huelen las nubes?

En Scala, es bastante común usar wildcards para obviar parameter types e incidir en que no es necesario conocer su tipo para ejecutar la lógica de nuestro método/clase. Algo del estilo:

val myArray: Array[_] = Array(1,2,3)

Antes de hablar de tipos existenciales en Scala, creo que hay que dar un pequeño disclaimer acerca de lo heavy que puede resultar (mejor que dos dormidinas con un traguito de anis).

tumblr_mgf4arKIkk1rfq0ndo1_500

Tipos existenciales

Supongamos un método que devuelve la cardinalidad de una colección de elementos. Si la colección es covariante en T

scala> def count(elements: List[Any]): Int = elements.length
count: (elements: List[Any])Int

no tendríamos problemas a la hora de invocar dicho método con

scala> count(List[Int](1,2,3))
res0: Int = 3

Sin embargo, si probamos con una colección invariante en T

scala> def count(elements: Set[Any]): Int = elements.size
count: (elements: Set[Any])Int

scala> count(Set[Int](1,2,3))
<console>:9: error: type mismatch;
 found   : scala.collection.immutable.Set[Int]
 required: Set[Any]
Note: Int <: Any, but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 3.2.10)
              count(Set[Int](1,2,3))
                            ^

Obvio. Ninguna sorpresa hasta ahora. Si queremos que nuestro método acepte cualquier tipo de elementos para Set, podemos parametrizarlo:

scala> def count[T](elements: Set[T]): Int = elements.size
count: [T](elements: Set[T])Int

scala> count(Set[Int](1,2,3))
res0: Int = 3

Sin embargo, esto nos obliga a añadir un parámetro que no aporta nada, solamente es una forma de obligar al compilador a que obvie el tipo contenido.

La alternativa que se propone es definir el parameter type dentro de la signatura del parámetro:

scala> def count(elements: Set[T] forSome {type T}): Int = elements.size
count: (elements: Set[_])Int

scala> count(Set[Int](1,2,3))
res1: Int = 3

Los tipos existenciales, por tanto, son lo que subyace al syntactic sugar de los wildcards. La equivalencia con notación “chachipiruli”:

scala> def count(elements: Set[_]): Int = elements.size
count: (elements: Set[_])Int

scala> count(Set[Int](1,2,3))
res2: Int = 3

La vuelta de tuerca

Cabe destacar que, sobre la definición del parameter type, podemos añadir restricciones (context bounds). Por ejemplo, si queremos definir un Set que acepte todos los tipos primitivos…

scala> def count(elements: Set[T] forSome {type T<:AnyVal}): Int = elements.size
count: (elements: Set[_ <: AnyVal])Int

Fijaos que la REPL ya nos devuelve el método declarado, pero con la notación “chachipiruli”. Podemos probar que los context bounds funcionan con:

scala> count(Set(1,2,3))
res3: Int = 3

scala> count(Set(new{}))
<console>:9: error: type mismatch;
 found   : AnyRef
 required: AnyVal
Note that implicit conversions are not applicable because they are ambiguous:
 both method ArrowAssoc in object Predef of type [A](self: A)ArrowAssoc[A]
 and method Ensuring in object Predef of type [A](self: A)Ensuring[A]
 are possible conversion functions from AnyRef to AnyVal
              count(Set(new{}))
                        ^

En cuanto intentamos pasar al método un AnyRef cualquiera…ZASCA: salta el compilador imperturbable sobre su presa cual guepardo.

91662

Una limitación al respecto, es no poder definir view bounds sobre el tipo T:

scala> def count(elements: Set[T] forSome {type T<%Int}): Int = elements.size
<console>:1: error: `=', `>:', or `<:' expected
       def count(elements: Set[T] forSome {type T<%Int}): Int = elements.size
                                                 ^

En este caso, tendríamos que volver a la idea original de parametrizar el método. Si ahora quisiéramos definir un método que sumara todos los elementos de un Set, y que dichos elementos fueran transformables a Double:

scala> def sum[T<%Double](elements: Set[T]): Double =
  (0.0 /: elements)((d1,d2) => d1 + d2)
sum: [T](elements: Set[T])(implicit evidence$1: T => Double)Double

scala> sum(Set[Int](1,2,3))
res9: Double = 6.0

scala> sum(Set[Float](1.0f,2.5f))
res10: Double = 3.5

La vuelta de tuerca (y en serio no se pueden dar más vueltas)

Si nos ponemos en tono filosófico, que ocurre si usamos el tipo existencial:

T forSome {type T}

982aad317c237d9fa918138b1c7bd020_1024

Sin entrar en debates sobre si el mundo es plano y se apoya sobre el lomo de cuatro elefantes que viajan a lomos de una tortuga cósmica….el tipo que hemos definido hace referencia a Any (Que detallaremos para los más lechones en futuros posts.).

Por tanto, ¿qué diferencia sustancial existe entre las siguientes definiciones?

Set[T] forSome { type T }
Set[T forSome { type T }]

my-eyes-messed-up_o_386883

El primer tipo hace referencia a todos los Set (independientemente del tipo que les parametriza) mientras que el segundo, como hemos dicho antes, representa Set[Any].

¿Sigues sin ver la diferencia? La invarianza la marca, y con código se ve mejor. Con la primera definición, ya utilizada en los primeros ejemplos de este post tenemos que:

scala> def count(elements: Set[T] forSome {type T}): Int = elements.size
count: (elements: Set[_])Int

scala> count(Set[Int](1,2,3))
res13: Int = 3

scala> count(Set[String]("hi","bye"))
res14: Int = 2

Pero si modificamos la definición del método, utilizando la segunda forma de las expuestas previamente, veremos que Set[Int] no es subtipo de Set[Any]:

scala> def count(elements: Set[T forSome {type T}]): Int = elements.size
count: (elements: Set[_])Int

scala> count(Set[Int](1,2,3))
<console>:9: error: type mismatch;
 found   : scala.collection.immutable.Set[Int]
 required: Set[T forSome { type T }]
Note: Int <: T forSome { type T }, but trait Set is invariant in type A.
You may wish to investigate a wildcard type such as `_ <: T forSome { type T }`. (SLS 3.2.10)
              count(Set[Int](1,2,3))
                            ^

¿Loco? Espero que lo suficiente para seguir leyendo nuestros próximos posts 🙂

¡Agur de naranja!

Anuncios

One thought on “Tipos existenciales ¿A qué huelen las nubes?

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s