Varianza en Ceylon

Bueno pues después del artículo maratónico acerca de varianza en Java, ahora quiero explicar cómo se implementó esto en Ceylon. Para ello vamos a usar los mismos ejemplos, de modo que quede clara la comparación.

Lo primero son las tres clases para el ejemplo, y la primera función:

 

Hasta aquí, todo funciona exactamente igual que en Java:

 

En Ceylon también existe la covarianza en los tipos de retorno, similar a lo que se introdujo en Java 5:

 

Con los parámetros no se puede hacer algo similar. Ceylon no tiene sobrecarga de métodos, pero no tiene contravarianza en los parámetros; por lo tanto, al refinar un método de un supertipo, los parámetros deben ser exactamente del mismo tipo que en la declaración original, o el compilador emite un error.

Ahora, entremos a la parte interesante, con los parámetros de tipo, con el contenedor simple y una función que lo utilice:

 

Al igual que en Java, solamente la segunda llamada es válida. Pero al menos, el error en Ceylon es bastante más entendible:

 

En Ceylon, la varianza se define en el sitio de declaración. Es decir, donde se declara el parámetro de tipo:

 

La sintaxis es muy simple: se usa   para marcar un parámetro de tipo como covariante, o   para marcarlo como contravariante. Si no se usa ninguno de los dos, es invariante. Y ahora, sin tener que modificar ese código, ya solamente la tercera línea falla:

 

Tal vez recuerden que en Java, pudimos forzar a la tercera línea marcándola como contravariante, para que compilara. En Ceylon esto no es posible, sólo se puede definir varianza en sitio de uso con parámetros de tipo invariantes. Lo bueno es que el error es bastante claro:

 

Ahora vamos a ver qué pasa con los parámetros, pero ya de una vez con el Cont covariante:

 

Ceylon no tiene el operador diamante de Java... tiene el operador invisible. Simplemente no es necesario indicar el argumento de tipo porque el compilador lo puede inferir: la primera llamada es a un   por ejemplo.

La primera línea, por cierto, nos da error; esto es porque el parámetro de   es un  , por lo que se le puede pasar un Cont con B o C. Recuerden que el parámetro de tipo de Cont fue declarado covariante, por lo que el parámetro   significa que acepta un Cont con B o cualquier subtipo de B.

Para contravarianza podemos usar un ejemplo concreto, en vez de imaginarnos cosas raras. En Ceylon hay varias interfaces que usan contravarianza; dos de ellas con Category y Comparable. Estos son unos fragmentos de las mismas:

 

Aprovecho para mencionar varias cosas que se ven aquí y que pueden llamarles la atención:

  • El parámetro de tipo   de   es contravariante, y por default será  . Los parámetros de tipo en Ceylon pueden tener defaults.
  •   tiene definido un límite:  . Es bastante obvio por lo que dice, pero en caso que no: significa que cualquier argumento de tipo que quieran usar para  , debe ser   (o un subtipo de Object). Es decir, no acepta nulos.
  • El parámetro de tipo   de   es un self type (honestamente no sé cómo traducir esto). Es la parte que dice   y, de manera muy simplificada, representa un tipo concreto de   (alguna implementación de esa interfaz). Sirve para limitar a que una implementación de Comparable solamente pueda tener como argumento de tipo al mismo tipo (p.ej.   sólo puede ser  ; si no tuviera esa restricción, se podría hacer que   fuera   cosa que no tiene sentido, sin embargo es posible en Java).

¿Por qué son contravariantes estas interfaces? Consideren esto:

 

Esto compila sin ningún problema porque   es contravariante, lo cual permite a   ser un   a pesar de que ya es un   lo cual la hace un  . Si Comparable fuera invariante, entonces el compilador arrojaría este error al compilar Y:

 

La clave es incompatible type arguments; son incompatibles porque el parámetro de tipo es invariante. Pero al hacerlo contravariante, entonces   significa que es Comparable con Y o cualquier supertipo de Y, y eso lo hace compatible con  .

Pero, este problema se resolvió en la definición de   Los usuarios de esa interfaz (es decir, quienes programen en Ceylon) no tienen que preocuparse por eso. Una clase que satisfaga la interfaz Y sólo tiene que implementar compare de esta forma:

 

Y se puede implementar una función de este tipo y luego invocarla pasándole argumentos de tipos dispares:

 

En cambio, los usuarios de Comparable en Java sí tienen que preocuparse por este tipo de cosas. Este código no compila en Java:

 

La única forma de hacer que compile es así:

 

Y para implementar ese método de comparación, hay que poner la contravarianza en el método:

 

Ahora simplemente díganme: ¿Cuántos de ustedes han escrito alguna vez un método con una firma similar? Es más: ¿Cuántos de ustedes han visto siquiera un método así? Seguramente muy pocos, y esto si de entrada la covarianza y sobre todo la contravarianza no son conceptos fáciles de entender, su uso en Java es bastante complicado. Por eso es una característica importante del sistema de tipos de Ceylon.

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de echan

Si es una decisión consciente

Si es una decisión consciente restringir la varianza en Ceylon me parace acertado, porque la mejor forma de no tener problemas de varianza es evitando la varianza.

Lo que quiero decir es que si la herencia y los genericos chocan y crean varianza es porque ambos conceptos tratan de resolver el problema de la abstracción pero de forma diferente. La pregunta es se puede usar solo una de las dos?

En Java tenemos
a) Polimorfismo ad-hoc con herencia, sobrecarga y dispatch dinámico
b) Polimorfismo parametrico con generics

y en Ceylon? Segun entiendo polimorfismo paramétrico es la mejor opción no?

Imagen de ezamudio

las dos

No hay sobrecarga de métodos en Ceylon, pero obviamente sí hay herencia, de hecho hay herencia múltiple porque las interfaces pueden tener métodos concretos. Por lo tanto, no hay polimorfismo ad-hoc, es polimorfismo de subtipos (o polimorfismo, a secas).

Si tienes una clase   así, invariante, entonces puedes usar varianza en sitio de uso igual que en Java, por ejemplo   o  . Pero, si el parámetro de tipo ya es covariante o contravariante, entonces no se puede definir varianza en sitio de uso (que es lo que mostré en el ejemplo arriba). A eso me refiero cuando hablaba de la restricción de varianza.

Do you know ...

▲ LOL

Fuente: New Features in C# 2010: Covariance and Contravariance and List ← ¿.NET? ¡Por favor, no le digan a mis amigos!