Alias e inferencia de tipos

Esta es la séptima parada en el tour de Ceylon. En la pasada entrega introducimos varios tipos de objetos iterables. Ahora es tiempo de explorar el sistema de tipos de Ceylon con más detalle.

En este capitulo, vamos a discutir los alias de tipos y la inferencia local de tipo, dos características que te ayudaran a reducir la verbosidad del código tipado estáticamente.

Alias de tipos

Es frecuentemente útil proveer un nombre mas corto o mas semántico a una clase o interfaz existente, especialmente si la clase o la interfaz es un tipo parametrizado. Para esto, utilizamos los alias de tipo.

Para definir un alias para una clase o interfaz, usamos la flecha gorda, por ejemplo:

interface People => set<Person>;

El alias de una clase deberá declarar sus parámetros formales:

class People({Person*} people) => ArrayList<Person>(people);

Si quieres crear un alias para un tipo unión o intersección tienes que usar la palabra reservada alias:

alias Num => Float|Integer;

Un tipo alias puede ser parametrizado, y tener tipos constraints, los cuales veremos más adelante:

class Named<Value>(String name, Value val)
        given Value satisfies Object
        => Entry<String,Value>(name,val);

Los alias nos ayudan a reducir la verbosidad, por que en vez de repetidamente escribir el mismo tipo generico, por ejemplo Set<Person> podemos usar un alias, tal como People, pero en algunos casos Ceylon nos permite omitir completamente el tipo.

Un alias de tipo en el toplevel o dentro de una clase o interfaz puede ser shared.

shared interface People => set <Person>

Inferencia de tipos

Hasta es momento, hemos estado especificando el tipo de cada declaración. Esto en general hace código, especialmente códigos de ejemplo, mucho mas fáciles de leer y entender.

Sin embargo, Ceylon tienen la habilidad para inferir el tipo de una variable local o el tipo de retorno de un método local. Solamente colocando la palabra reservada value en el caso de una variable local o function en el caso de un método local en lugar de el tipo de la declaración.

value polar = Polar(pi, 2.0);
value operators = { "+", "-", "*", "/" };
function add(Integer x, Integer y) => x+y;

Existen algunas restricciones al aplicar esta característica. Tu no puedes usar value o function.

  • para declaraciones anotadas con shared,
  • para declaraciones anotadas con formal,
  • cuando el valor es especificado después en el bloque de la declaración, or
  • para declarar un parámetro.

Estas restricciones significan que las reglas de inferencia de tipo en Ceylon son algo simples. La inferencia de tipos es puramente “derecha-a-izquierda” y “arriba-a-abajo”. El tipo de una expresión ya es conocido sin la necesidad de buscar cualquier tipo declarado a la izquierda del especificar =, o mas adelante el bloque de declaración.

  • El tipo inferido de una referencia declarada value es solo el tipo de la expresión asignada a ella usando =.
  • El tipo inferido de una referencia declarada value es solo la unión de los tipos de la expresión devuelta en la declaración return del getter.
  • El tipo inferido de un método declarado function es solo la unión de los tipos de la expresión devuelta que aparecen en la declaración return del método.

Inferencia de tipos al construir expresiones iterables

Que hay acerca de construir expresiones iterables como esta:

value coords =  { Polar(0.0, 0.0) , Cartesian(1.0, 2.0) };

¿Qué tipo es inferido para coords? Tal vez tu respuesta sea:

{X+} donde x es el superclase común o super interfaz de los tipos de todos los elementos.

Pero esto puedo que no sea correcto, desde que puede haber mas de un supertipo común.

La respuesta correcta es que la inferencia de tipos es {X*} donde x es la unión de los tipos de todas la expresiones de los elementos. En este caso, el tipo es {Polar,Cartesian*}. Ahora, esto es una buena solución, debido a que Iterable<T> es covariante en T. Así el siguiente código esta bien tipado.

value coords =
    { Polar(0.0, 0.0),
      Cartesian(1.0,2.0) }; //type {Polar|Cartesian+}
{Point*} points = coords;

Al igual que el siguiente código:

value nums = { 12.0, 1, -3 }; //type {Float|Integer+}
{Numbers+} numbers = nums;

¿Qué hay acerca de los iterables que producen null? Bueno, ¿recuerdas que el tipo de null es Null?

value string = { null, "Hello", "world" }; //type: {String?+}
String? str = strings.first;

El tipo de el atributo first de Iterables<Element> es Element?. Aquí, tenemos Iterable<String?>, substituyendo String? a Element, y así obtenemos el tipo String?? la cual es Null|Null|String que simplemente es Null|String, puede ser escrito como String?. Por supuesto el compilador puede figurarse que tipo queremos, y puede ser simplificado:

value string = { null, "hello", "World" }; //type: {String?+}
value str = strings.first; //type: String?

El mismo trabajo funciona para secuencias:

value tuple = [null, "Hello","World"]; //type: [Null,String,String]
String?[] strings = tuple;
value str = strings[0]; //type String?

Es interesante que tan útil los tipos unión pueden ser. Incluso si raramente escribes código con declaraciones unión explicitas, ellos aun estarán hay, en el trasfondo, ayudando al compilador a solucionar algunos descabellados y otras veces ambiguos, problemas de tipos.

Note que lo que hemos visto es realmente solo un caso especial de el algoritmo que Ceylon usa para inferencia de argumentos para tipos generic, y todo lo anterior también funciona para tipos generic escritos por el usuario al igual que lo hace para Iterable.

Clases anónimas e inferencia de tipos

Desde que una clase anónima no tiene un nombre, Ceylon remplaza clases anónimas con la intersección de sus supertipos cuando lleva acabo la inferencia de tipos.

interface Foo {}
interface Bar {}
object foobar satisfies Foo&Bar {}
value fb = foobar; //inferred type Basic&Foo&Bar
value fbs = { foobar, foobar }; // inferred type {Basic&Foo&bar+}

Aun hay mas

En lo siguiente, vamos a explorar mas detalles de el sistema de tipos, comenzando con tipos unión, intersección, enumerated y switching. Entonces, despues de ello estaremos listos para discutir tipos generic.