You are on page 1of 87

Un tour por Ceylon

Release

Miguel Angel Gordian

31 de July de 2014
Índice general

1. El lenguaje de programación ceylon 3


1.1. Un clasico hola mundo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2. Compilacion del programa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3. Fundamentos del lenguaje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.4. Funciones y valores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
1.5. Numeros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

2. Clases 13
2.1. Nombrando a los identificadores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13

3. Atributos y variables, estrucutras de control 19


3.1. Atributos y valores locales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.2. Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.3. Setters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.4. Estructuras de control . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21

4. Herencia, refinamiento, e interfaces 25


4.1. Herencia y refinamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
4.2. Interfaces y herencia “múltiple” . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

5. Clases anónimas y miembro 33


5.1. Clases anónimas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
5.2. Clases miembro y su refinamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34

6. Iterables, secuencias y tuplas 37


6.1. Iterables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
6.2. Secuencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
6.3. Rangos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
6.4. Tuplas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41

7. Alias e inferencia de tipos 43


7.1. Alias de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
7.2. Inferencia de tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44

8. Unión, intersección y tipos enumerados 47


8.1. Reduciendo el tipo de un objeto referencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
8.2. Tipos intersección . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
8.3. Tipos unión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
8.4. Tipos Enumerado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

I
9. Generics 53
9.1. Definiendo tipos genéricos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
9.2. Argumentos de tipo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
9.3. Covarianza y contravarianza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54

10. Paquetes y módulos 59


10.1. Paquetes e importaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
10.2. Módulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
10.3. Ejecutando un módulo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62

11. Funciones 65
11.1. Clases de primer orden y de orden superior . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
11.2. Representado el tipo de una función . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
11.3. Definiendo funciones de orden superior . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
11.4. Referencias a funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
11.5. Curring . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
11.6. Funciones anonimas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
11.7. Mas acerca de funciones de orden superior . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
11.8. Composición y curry . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
11.9. El operador spread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
11.10. Aún hay más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

12. Argumentos con nombre 75


12.1. Argumentos con nombre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
12.2. Argumentos iterables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
12.3. Dejando fuera los nombres de los parámetros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
12.4. Sintaxis declarativa para instanciar objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
12.5. Definiendo intefaces de usuario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
12.6. Aún hay más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79

13. Comprehensions 81
13.1. Comprehensions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
13.2. Transformación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
13.3. Filtrado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
13.4. Productos y uniones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
13.5. Aún hay más . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82

II
Un tour por Ceylon, Release

Esta es el intento de traducción(no muy bueno) al español de el Tour de Ceylon, el cual lo puede encontrar en la pagina
oficial del lenguaje Ceylon.
Pretende ser una guia de iniciación a las poderosas características que provee Ceylon.
El contenido de esta pagina se encuentra en bitbucket en el repositorio CeylonTour, donde toda ayuda es bienvenida y
agradecida.

Índice general 1
Un tour por Ceylon, Release

2 Índice general
CAPÍTULO 1

El lenguaje de programación ceylon

1.1 Un clasico hola mundo

Comenzemos escribiendo en nuestro editor preferido en un archivo llamado hola.ceylon el siguiente fragmento de
codigo:
void hola(){
print("hola mundo!");
}

La sintaxis es muy similar a la de java, esta es la manera de definir una función en este caso solo imprime un mensaje
“Hola mundo!”, un clasico, pero en este momento no me enfocare en la sintaxis de la funcion si no en su rol dentro
del programa.
Esta funcion es conocida toplevel function por que no es miembro del algun tipo de dato. Esto quiere decir que no
necesitamos crear un objeto para poder utilizar la funcion hola, solo necesitas llamarla de la sig. forma:
hola()

Ceylon no tiene metodos estaticos como Java o C++ pero se pueden pensar que las funciones toplevel puden servir
para el mismo proposito. La razon que las diferencia es que ceylon tiene una structura de bloques muy estricta - un
bloque anidado siempre tiene acceso a las declaraciones en todos los bloques que la contienen. Esto no es el caso con
los metodos estaticos de Java.
De acuerdo a la documentación, Ceylon por el momento no soporta scripting, y no podemos escribir codigo fuera de
clases y funciones.

1.2 Compilacion del programa

Es importante tener un area de trabajo asi que por limpieza crearemos una carpta llamada ceylon donde mantendremos
la estructura que necesita ceylon para trabajar.
Dentro de la carpeta ceylon crearemos una llamada source y dentro de ella colocaremos el archivo hola.ceylon. A
continuacion mostraremos la manera de compilar el programa.
$ ceylon compile source/hola.ceylon
Note: Created module default

Durante la compilación se crearan las carpeta llamada modules/default, las cuales contendran los bytecodes de nuestro
programa.

3
Un tour por Ceylon, Release

$ ceylon run --run hola default


hola mundo!

Con estos ejecutaremos el programa, la descripcion es simple:


run : Le indica a el comando ceylon que ejecutaremos un programa
–run [Le indicamos a ceylon que el punto de entrada el cual puede] ser una funcion toplevel o una clase.
default [Es el nombre del modulo que ejecutaremos, por defecto es] default.
Este modulo tienen por nombre defult.car y se encuentra en el directorio modules/default.
LA documentación oficial recomienda hecharle un ojo a la ayuda proporcionada por ceylon.
$ ceylon help compile
$ ceylon help run

1.3 Fundamentos del lenguaje

1.3.1 Sintaxis, como aperitivo

Sin duda los elementos basicos forman la base de estructuras complejas es por eso que antes de pasar a elementos
propios de caylon debemos entender sus pilares.
Comenzemos hablando de los literales, los cuales son datos que podemos escribir en el programa y el compilador se
encargara de transformalo para poder trabajar sobre el.

1.3.2 String literal

La forma de escribir una string literal dentro de nuestro programa, es colocando el texto deseado dentro de comillas
dobles, como lo hemos hecho anteriormente en nuestro programa.
void hola() {
print("Hola mundo!");
}

Las cadenas en ceylon tienen un comportamiento especial, por ejemplo:


void hola() {
print("Hola
mundo!");
}

Se mostrara como:
Hola,
Mundo!

Cabe destacar que apesar de que en la segunda linea contenia espacios hasta el primer carater de esa linea, ceylon se
encarga de remover estos espacios, Esto ayuda a darle un formato al codigo mas agradable a la vista.
Es frecuente querer quitar(collapse) todos los espacios en blanco de una string literal, para ello Ceylon nos provee de
una clase String, la cual tiene un atributo llamado <normalized “”>:

4 Capítulo 1. El lenguaje de programación ceylon


Un tour por Ceylon, Release

void hola() {
value mensaje = "Hola,
mundo";
print(mensaje.normalized);
}

El cual da una salida como la siguiente:


Hola mundo!

1.3.3 Documentación, como una prioridad

Siempre es bueno agregar algo de documentacón, sobre todo si son funciones imporantes como hola().
Una forma de hacerlo is usando un comentario estilo C, como el siguiente:
/* El clasico programa hola mundo*/
void hola() {
print("Hola mundo!\n");
}

o como esta:
// El clasico programa hola mundo
void hola() {
print("Hola mundo!\n");
}

Pero es mucho mejor usar la anotacion doc para comentarios que describen declaraciones.
doc ("El clasico hola mundo")
by ("Zoek")
see (adios)
throws (IOException)
void hola() {
print("Hola mundo!\n");
}

Las anotaciones doc, by, see, y tagged contienen documentación que es incluida en la salida de la herramienta de
documentación de Ceylon, ceylon doc.
Anotaciones como doc, by, see, throws son son palabras reservadas, ellos son solo identificadores ordinarios. Lo mismo
aplica para identificadores que son parte de la definicion del lenguaje, por ejemplo: abstract, variable, shared, formal,
default, actual, etc.
La anotacion doc es ubicuo(omnipresente) los parentesis y la anotacion pueden ser obviadas cuando es la primera
anotación en la lista de anotaciones:
"El clasico hola mundo"
by ("Zoek")
see (adios)
throws (IOException)
void hola() {
print("Hola mundo!\n");
}

La anotación doc pueden ser escritas on formato Markdown.

1.3. Fundamentos del lenguaje 5


Un tour por Ceylon, Release

"El Clasico [Programa Hola mundo][holamundo]


que imprime un mesaje a la consola, esta vez
escrito en [Ceylon][].

Este simple programa demuestra:

1. Como definir una funcion toplevel, y


2. como imprimir con ‘print()‘ una literal ‘String‘.

Tu puedes compilar y ejecutar ‘hello()‘ desde la


linea de comandos:

ceylon compile source/hola.ceylon


ceylon run -run hola default

[holamundo]: http://en.wikipedia.org/wiki/Hello_world_program
[Ceylon]: http://ceylon-lang.com"

void hola () {
print("Hola mundo!\n");
}

Debes tener cuidado al indentar las multistring literal pues Markdown es sensible a la primera columna en que el texto
aparece.

1.3.4 Secuencias de escape

Dentro de las string literals, puedes usar secuencias de escape como n, t, \, “


print("\"Hola!\", dijo el programa");

Tambien puedes usar secuencias de escape hecadecimales de 2-bytes y 4-bytes.


"La constante matematica \{#03C0}, el
el radio de la circunferencia de un circulo
a su diametro."
Float pi=calculatePi();

"La constante matematica \{#0001D452},


la base del logaritmo natural."
Float e=calculateE();

Las Ceylon strings son compuestas de UTF-32 caracteres, pero esto lo retomaremos mas adelante.

1.3.5 Strings Verbatim

Algunas veces, las interpolación de secuencias de escape puede ser molesto, por ejemplo en aquellas veces que em-
bebemos codigo dentro de las string literals. Si utilizamos tres comillas dobles, “”“, para delimitar nuestra cadena,
obtendremos una string verbatim, que puede contener backlash no escapados y comillas dobles:
print(""""Hola!", dijo el programa.""");

1.3.6 String Interpolacion y concatenacion

Supongamos que queremos conocer mejor a nuestro programa, que nos habla de el:

6 Capítulo 1. El lenguaje de programación ceylon


Un tour por Ceylon, Release

"The Hello World program ... version 1.1!"


void hello() {
print("Hola, Este es Ceylon ‘‘language.version‘‘
corriendo sobre Java ‘‘process.vmVersion‘‘!\n
Me corriste en ‘‘process.milliseconds‘‘ ms,
con ‘‘process.arguments.size‘‘ argumentos.");
}

Notese como nuestro mensaje contiene expresiones interpoladas, delemitadas por doble acentos graves, ‘‘, estas son
conocidas como string templates.
En mi maquina, este programa da la siguiente salida:
Hola, Este es Ceylon 0.5
corriendo sobre Java 1.7!

Me corriste en 1374960853214 ms,


con 0 argumentos.

Otro elemento importante hablando de cadenas es la concatenación, esta atravez del operador +:
print("Hola, Este es Ceylon " + language.version +
"corriendo sobre Java " + process.vmVersion + "!\n" +
"Me corriste en " + process.milliseconds.string
+ " ms, con " + process.arguments.size.string +
" argumentos.");

Notese que hemos tenido que invocar explicitamente al atributo string para convertir expresiones numericas a cadenas.
EL operador + no convierte automaticamente sus operandos a cadenas.

1.3.7 Lidiando con objetos que no existen

Vamos a tomar un nombre como entrada desde la linea de comandos y necesitamos cubrir el caso donde nada ha sido
especidicado, esto nos da la oportunidad de explorar como los valores null son tratados en ceylon.Consideremos un
overlay-verbose ejemplo para comenzar:
"Print a personalized greeting"
void hello() {
String? name = process.arguments.first;
String greeting;
if (exists name) {
greeting = "Hello, ‘‘name‘‘!";
}
else {
greeting = "Hello, World!";
}
print(greeting);
}

El tipo String? indica que nombre puede contener un valor null. Entonces usaremos bloque if para manejar el caso de
un valor null separandolo del de un valor no null.
Pero tambien es posible abreviar el codigo, delarando localmente al nombre dentro de la codicion del if:
String greeting;
if (exists name =
process.arguments.first) {
greeting = "Hello, ‘‘name‘‘!";

1.3. Fundamentos del lenguaje 7


Un tour por Ceylon, Release

}
else {
greeting = "Hello, World!";
}
print(greeting);

Esta es estilo preferido la mayor parte de la veces, puesto que no usaremos nombre para alguna otra cosa fuera de la
condicional (pero esta no es la manera mas compacta de escribir el codigo).

1.3.8 Tipo opcionales

A diferencia de java, locales, parametros y atributos que pueden contener el valor null deberan de ser declaradas
explicitamente como de tipo opcional (la sintaxis T?). A su vez no existe una manera de asignar un valor null a una
variable local que no sea de tipo opcional, el compilador no debera permitirlo. Este es un error.
String name = null; // compile error: null is not an instance of String

El compilador de ceylon no te permitira hacer algo peligroso con un valor de yipo T? - esto es nada que puede causar
un NullPointerException en Java - Sin primero revisar que el valor no es null usando un if (exists ...). El siguiente
codigo tambien es un error.
String? name = process.arguments.first;
print("Hello " + name + "!"); // compile error: name is not Summable

De hecho, no es posible usar el operador == con una expresion de tipo opcional asi es que no podemos escribir lo
siguiente sin causar un error.
String? name = process.arguments.first;
if ( name == null) { ... } // compile error: name is not Object

En un leguaje con tipado estatico, siempre quieres conocer que tipo tienen los objetos. Entonces, ¿Cual es el tipo de
null?
La respuesta simple es: null es de tipo Null
Asi como se lee, el valor null no es valor primitivo en Ceylon, es una instacia ordinaria de una clase ordinaria Nul, al
menos desde el punto de vista del sistemas de tipos de Ceylon.
Y la sintaxis String? es solo una abreviacion del tipo union: Null|String.
Dado esto, podemos entender claramente por que no podemos hacer operaciones de String en String?. Simplemente
son tipos distintos. La construcción if (exists ..., reduce el tipo de nombre dentro del bloque if ‘ permitiendo tratarlo
como de tipo ‘String.
(si eres de los que se preocupan por el performace, esta de mas mencionar que el compilador de Ceylon hace algun
tipo de magia especial para transformar este valor a un null a nivel de la maquina virtual.

1.3.9 Operadores para el manejo de los null

Existen in conjunto de operadores que pueden ser de ayuda cuando se tratan con valores null. El primero es else:
String greeting = "Hello," + (name else "World");

El operador else da como resultado:


El primero operador si no es null, or
El segundo operado si sí lo es.

8 Capítulo 1. El lenguaje de programación ceylon


Un tour por Ceylon, Release

Es mas conveniente manejar valores null, en casos simples. Tambien puedes crear una cadena de else:
String name = firstName else userId else "Guest";

Tambien existe un operador para producir un valor null:


String? name = !arg.trimmed.empty then arg;

El operador then produce:


El segundo operador si el primer operando evalua a verdadero, o
null si no es asi.
Tambien puedes ligar un else despues de un then para reproducir el comprotamiento del operador ternario en C:
String name = !arg.trimmed.empty then arg else "World!";

Usando el operador else, podemos simplificar nuestro ejemplo a algo mas razonable:
"Imprime un mensaje personalizado"
void hola() {
print("Hola, ‘‘process.arguments.first else "World"‘‘!");
}

Despues de todo solo es una linea.

1.4 Funciones y valores

Los dos elementos mas basicos encontrados en todos los lenguages de programacion son las funciones y las variables.
En ceylon, las “variables” por defecto solo se pueden asignar una vez. Esto es, que las variables no pueden ser asignadas
a otro valor despues de que han sido asignadas a un valor por primera vez.
Sin embargo, usaremos la palabra value para referirnos a “variables” en general y reservar la palabra variable para
referirnos a un value que explicitamente definido para ser reasignable.
String adios = "adios"; // un value
variable Integer contador = 0; // una variable

adios = "Adeu"; // compile error


count = 1; // permitido

Notese que un value que no es una variable en este sentido, puede aun ser una “variable” en el sentido que su valor
varia entre dinstintas ejecuciones del programa o entres contextos dentro de la ejecuion de un programa.
Un value puede ser recalculado cada vex que es evaluado.
String nombre = { return primerNombre + " " + segundoNombre; }

Si el valor de primerNombre y segundoNombre varia entonces el valor de nombre tambien varia entre evaluaciones.
Una funcion toma un paso adeante en esta idea,El valor de una funcion depende no unicamente del contexto en que es
evaluado pero tambien el argumento a los parametros.
Float sqr (Float x) { return x * x; }

En ceylon la declaración de un valor o de una funcion puede ocurrir en casi cualquier lugar : Como un toplevel, pertene
a un paquete directamente, como un atributo o un metodo de una clase o como una declaracion local de bloque dentro
un different value o el cuerpo de una función, De hecho, como veremos mas adelante, la declaracion de un value o
funcion pueden ocurrir dentro de una expresion en algunos casos.

1.4. Funciones y valores 9


Un tour por Ceylon, Release

La declaracion de funciones es muy similar a la que probablemente hallas usado en otros lenguajes parecidos a C, pero
con dos excepciones. Ceylon has:
Parametros por defecto, y
parametros variables(variadic)

1.4.1 Parametros por defecto

Un parametros de una funcion puede especificar un valor por defecto.


void hola (String nombre="Mundo") {
print("Hola ‘‘nombre‘‘!");
}

Entonces no necesitamos forsosamente pasarle argumentos cuando se llama a la función.


hola(); // Hola mundo!
hola("JBoss); // Hola JBoss!

Parametros por defecto deberan de ser declarados despues de los parametros requeridos en la lista de parametros.

1.4.2 Parametros variables

Un parametro variadic de una funcion o clase es declarado usando un asterisco posfijo, por ejemplo String*. Una
funcion o clase solo puede tener un parametro variadic y debera ser el ultimo parametro.
void HolaTodos (String* nombres) {
//...
}

Dentro del cuerpo de la funcion, el parametro nombres tiene el tipo [String*], un tipo sequence, del cual hablaremos
mas tarde. Para acceder a cada uno de sus elementos podemos iterar sobre el utilizando un ciclo for.
void holaTodos (String* nombres) {
for (nombre in nombres) {
hola(nombre);
}
}

Para pasar un argumento a un parametro secuenciado tenemos tres alternativas. Podemos:


Proveer una lista explicita de argumentos,
pasar un objeto iterable produciendo el argumentos,o
especificando un comprehencion
El primer caso es facil:
holaTodos("world", "marte", "saturno");

Para el segundo caso, Ceylon requiere utilizar el operador spread:


String[] todos = ["world", "marte", "saturno"];
holaTodos(\*todos);

Volveremos a el tercer caso, comprenhencion, mas adelante.

10 Capítulo 1. El lenguaje de programación ceylon


Un tour por Ceylon, Release

1.4.3 Fat arrows y declaracion adelante

Las expresiones en Ceylon son mas poderosas que las de Java, y dado esto es posible expresar mas en una expresion
mas compacta y es por lo tanto extremadamente comun encontrar funciones y valores que simplemente evaluan y
devuelven una expresion. Asi que Ceylon nos permite abreviar tales definiones de funciones y valores usando una fat
arrow, =>. Por ejemplo:
String nombre => PrimerNombre + " " + ultimoNombre;

Float sqr (Float) => x*x;

Ahora es el tiempo para que te empiezes a sentir comodo con esta sintaxis, debido a que estar viendo comunmente
muchos ejemplo de esto. Toma especial atencion a la diferencia entre un fat arrow:
String nombre => nombre + " " + apellido;

Y una asignación:
String nombre = nombre + " " + apellido;

En el primer ejemplo, la exprecion es recomputada cada vez que nombre es evaluado. En el segundo ejemplo la
expresion es computada una sola vez y el resultado es asignado a nombre.
Tambien podemos definir uan funcion de tipo void usando una fat arrow. En nuestro primer ejemplo pudimos haber
escrito hola() como lo siguiente:
voide hola() => print("Hola mundo!);

En java y C#, nos permite separar la declaracion de una variable y la iniciación de su valor. Hemos visto que esto es
permitido en ceylon. Asi que podemos escribir:
String nombre;
nombre = nombrePila + " " + apellido;
print(nombre);

Pero en Ceylon tambien nos permite hacerlo con fat arrow:


Debido a un bug en M5, el siguiente ejemplo actualmente no es complatiable para la JVM.
String nombre;
nombre => nombrePila + " " + apellido;
print(nombre);

e incluso con funciones:


Float sqr(Float x);
sqr(Float x) => x*x;
print(sqr(0.01));

void hola();
hola() => print("hola mundo");
hola();

El compilador se asegura de no evaluar un valor o invocar a una funcion antes de asignarle un valor o especificar su
implentación, como veremos mas adelante. (Por que si lo hacemos, deberia de resultar en un NullPointerException, el
cual !Ceylon no tiene!)

1.4. Funciones y valores 11


Un tour por Ceylon, Release

1.5 Numeros

Desafotunadamente, no todos los programas son simples y elegantes com “hola mundo!”. En los negocios o compu-
tación cientifica, podemos encontrar comunmente programas que resuelven complicadas cosas con numeros. Ceylon
no tiene ninguna tipo primitivo, asi que los numero son usualmente representados por las clases Integer y Float,
volveremos mas tarde sobre este concepto.
las literales Float, son escritas con punto decimaly los enteros sin el.
Integer uno = 1;
Float zero = 0.0;

Incluso aunque son clases, puedes usarlos como tipicos literales numericos y operadores con ellos.
Por ejemplo, la siguiente funcion eficientemente determina si un Integer representa un numero primo:
"Determine if ‘n‘ is a prime number."
throws (Exception, "if ‘n<2‘")
Boolean prime(Integer n) {
if (n<2) {
throw Exception("illegal argument \‘‘n‘‘<2");
}
else if (n<=3) {
return true;
}
else if (n%2==0 || n%3==0) {
return false;
}
else if (n<25) {
return true;
}
else {
for (b in 1..((n.float^0.5+1)/6).integer) {
if (n%(6*b-1)==0 || n%(6*b+1)==0) {
return false;
}
}
else {
return true;
}
}
}

Intentalo corriendo la siguiente funcion:


"Muestra una lista de todos los numeros de dos digitos primos"
void EncuentraPrimos() {
print([ for (i in 2.99) if (prime(i)) i ]);
}

Este fue solo un pequeño rompecabezas para mantener tu interes, Explicaremos la sintaxis que usamos aqui mas
adelante.

1.5.1 Aun hay mas

Ceylon es un lenguage orientado a objetos, asi que una gran cantindad de codigo que escribamos en Ceylon es conte-
nido en clases. Aprendamos acerca de las clases justo ahora, antes de volver a mas conceptos basicos.

12 Capítulo 1. El lenguaje de programación ceylon


CAPÍTULO 2

Clases

Este es la segunda parada en nuestro tour por el lenguaje de programación Ceylon. En el capitulo anterior has aprendido
la básico acerca de la sintaxis de Ceylon. En esta parada, aprenderemos como definir clases con métodos y atributos.

2.1 Nombrando a los identificadores

El caso del primer carácter de un identificador es importante. Los nombres de los tipos (interface, class, type parameter)
deberán de comenzar la letra inicial con mayúscula. Los nombres de las funciones y valores comenzaran con minúscula
o guión. El compilador de ceylon es muy exigente respecto a este aspecto, así que obtendrás un error de compilación.
class hola() { ... } //compile error

o
String Name = .... //compile error

Esta es una manera de sobrellevar las restricciones, la cual es muy útil cuando trabajamos cuando trabajamos con Java.
Puedes “forzar” a el compilador a entender que un identificador es el nombre de un Tipo de dato pre fijándolo con I, o
que es el nombre de una función o valor con el prefijo i. Por ejemplo, iRojo es considerado un identificador inicial en
minúscula.
Las siguientes declaraciones son aceptables, pero definitivamente no son recomendadas, excepto en un escenario
interoperativo.
class \Ihola() { ... } //compile error

o
String \iName = .... //compile error

2.1.1 Creando tus propias clases

Nuestra primera clase haremos que represente un punto en un sistema de coordenadas polares. Nuestra clase toma dos
parámetros, dos métodos y un atributo.

13
Un tour por Ceylon, Release

class Polar (Float angulo, Float radio) {

shared Polar rotate(Float rotation) =>


Polar(angle + rotation, radius);

shared Polar dilate(Float dilation) =>


Polar(angle, radius*dilation);

shared String description = "(‘‘radius‘‘,‘‘angle‘‘)";

Debemos hacer notar dos cosas en particular en el código:


1. Los parámetros usados para instanciar la clase son especificados como parte de la declaración de la clase. No
existe un constructor al estilo de Java en Ceylon. Esta sintaxis es menos verbosa y mas regular que la de Java,
C# o C++.
2. Podemos hacer uso de los parámetros de una clase en cualquier lugar del cuerpo de la clase. En Ceylon, común-
mente no necesitamos definir explícitamente miembros de la clase para mantener valores. De hecho, podemos
accesar a los parámetros angulo y radio directamente dentro de los métodos rotar y dilatar, y desde la expresión
que especifica el valor de descripcion.
Nótese también que Ceylon no tienen una palabra reservada new para indicar la instanciación, solo se necesita escribir
Polar(angulo, radio), para invocar a la clase.
La anotación shared determina la accesibilidad de el tipo de dato anotado, atributo o método. Antes de seguir avan-
zando, veamos como podemos ocultar la implementación interna de una clase a otros códigos.

2.1.2 Escondiendo los detalles de la implementación

Ceylon no hace dinstinciń entre public, protected y default visibilidad como java lo hace; Aqui el porque En vez de
ello, el lenguaje distingue entre:

Elementos del programa que son visibles unicamente en el scope o alcance donde ellos fueron definidos, y
Elementos del programaque son visibles donde sea que el objeto donde el pertenece sea visible.
Por defecto, los miembros de la clase son ocultados para todos afuera de la definición de la clase. Por anotación, un
miembro con la anotación shared, es declarado para ser visible a cualquier código donde la clase sea visible.
Así pues, una clase puede ser ocultada de otro código. Por defecto una clase toplevel son ocultadas hacia fuera del
código donde la clase fue definida. Anotando a una clase top level la hace visible a cualquier código donde el paquete
que la contiene es visible.
Finalmente, los paquete son ocultos del código fuera del modulo al que el paquete pertenece por default. Únicamente
paquetes compartidas explícitamente son visibles a otros módulos.
¿Captas la idea? Hemos estado jugando muñecas rusas.

14 Capítulo 2. Clases
Un tour por Ceylon, Release

2.1.3 Exponiendo parámetros como atributos

Si queremos mostrar el angulo y el radio de nuestra coordenada Polar a otro código, necesitamos definir los atributos
de la clase. Es común asignar los parámetros de una clase directamente a un atributo shared de la clase, así que Ceylon
nos provee de una sintaxis simplificada para este propósito.
"A polar coordinate"
class Polar(angle, radius) {

shared Float angle;


shared Float radius;

shared Polar rotate(Float rotation) =>


Polar(angle+rotation, radius);

shared Polar dilate(Float dilation) =>


Polar(angle, radius*dilation);

shared String description = "(‘‘radius‘‘,‘‘angle‘‘)";

Todo código que use Polar puede acceder a los atributos de la clase usando una sintaxis muy conveniente.
shared Cartesian cartesian(Polar polar) {
return Cartesian(polar.radius*cos(polar.angle),
polar.radius*sin(polar.angle));
}

Incluso existe una manera mas compacta de escribir el código anterior, aunque en un poco menos legible.
"A polar coordinate"
class Polar(shared Float angle, shared Float radius) {

shared Polar rotate(Float rotation) =>


Polar(angle+rotation, radius);

shared Polar dilate(Float dilation) =>


Polar(angle, radius*dilation);

shared String description = "(‘‘radius‘‘,‘‘angle‘‘)";

Esto ilustra una importante característica de Ceylon: No hay una suficiente diferencia esencial aparte de la sintaxis,
entre parámetros de una clase y una valor declarado en el de una clase.
En vez de declarar los atributos en el cuerpo de una clase, simplemente podemos anotar el parámetro con shared.
Nosotros recomendamos que evites esta sintaxis cuando tengas más de uno o dos parámetros.

2.1.4 Inicializando los atributos

Los atributos angulo y radio son referencias, y es lo mas cercano que tienen Ceylon a un field en Java. Usualmente
declaramos el valor de una referencia cuando la declaramos.
shared Float x = radio * sin(angulo);
shared String Saludo = "Hola, ‘‘nombre‘‘";
shared Integer meses = anios * 12;

2.1. Nombrando a los identificadores 15


Un tour por Ceylon, Release

En algunas veces se tiene que separar la declaración de la asignación.


shared String descripcion;
if (exists etiqueta) {
descripcion = etiqueta;
}
else {
descripcion = "(‘‘radio‘‘,‘‘angulo‘‘)";
}

Pero si no existe un constructor en Ceylon, ¿dónde deberiamos de poner este código? Debemos de ponerlo dentro del
cuerpo de la clase.
"Una cordenada polar con una etiqueta opcional"
class Polar(angulo, radio, String? etiqueta) {

shared Float angulo;


shared Float radio;

shared String descripcion;


if (exists etiqueta) {
descripcion = etiqueta;
}
else {
descripcion = "(‘‘radio‘‘,‘‘angulo‘‘)";
}

// ...
}

EL compilador de Ceylon te fuerza a especificar un valor de cualquier referencia antes hacer uso de la referencia en
una expresión.
Integer contador;
void inc() {
contador++; //compile error
}

Pero hablaremos mas de esto mas adelante.

2.1.5 Abstrayendo estados usando atributos

Si estas acostumbrado a usar JavaBeans, puedes entender a las referencias como una combinación de varias cosas.
Un field
Un getter, y , algunas veces,
Un setter
Esto es porque no todos los valores son referencias como las que hemos visto; otras son como un método get, o algunas
veces como un par de métodos get y set.
Nosotros necesitamos mostrar un equivalente de coordenadas cartesianas de una polar. Desde que las coordenadas
cartesianas pueden ser computadas desde coordenadas polares, no necesitamos definir referencias state-holding. En
vez, podemos definir los atributos como getters.
"Una coordenada polar"
class Polar(angulo, radio) {

16 Capítulo 2. Clases
Un tour por Ceylon, Release

shared Float angulo;


shared Float radio;

shared Float x => radio * cos(angulo);


shared Float y => radio * sin(angulo);

// ...

Nótese que la sintaxis de la declaración de un getter se muestra como la declaración de método sin una lista de
parametros.
Entonces, ¿en qué manera son atributos “abstrayendo el estado”? Buenos, los códigos que utilicen un elemento Polar,
no necesitan si es un atributo o un getter. Ahora que conocemos acerca de los getters, podemos reescribir nuestro
atributo decripcion como un getter, sin afectar a cualquier código que lo use.
"Una cordenada polar con una etiqueta opcional"
class Polar(angulo, radio, String? etiqueta) {

shared Float angulo;


shared Float radio;

shared String descripcion {


if (exists etiqueta) {
descripcion = etiqueta;
}
else {
descripcion = "(‘‘radio‘‘,‘‘angulo‘‘)";
}
}

// ...
}

2.1.6 Viviendo sin overloading

Pienso que es tiempo para las malas noticias: Ceylon no tiene sobrecarga de métodos o constructores (La verdad es que
la sobrecarga es la fuentes de varios problemas en Java, especialmente cuando generics están en juego). Sin embargo
podemos emular muchos usos no perjudiciales de la sobrecarga de constructores y métodos usando:
Parámetros por defecto
Variadic parámetros (varargs), y
Tipos union y enumerated constraints
En este momentos no profundizaremos en detalles en cada una de las tres, pero veremos un pequeños ejemplo de cada
una de las tres técnicas.
// parámetros por defaul
void println(String linea, String eol = "\n") =>
process.write(line + eol);

// Variadic parametros
void printlns(String* lineas) {
for (linea in lineas) {
println(linea);

2.1. Nombrando a los identificadores 17


Un tour por Ceylon, Release

}
}

// Tipo Union
void imprimeNombre(String|Named nombre) {
switch (nombre)
case (is String) {
println(nombre);
}
case (is Named){
print(nombre.pila + " " + nombre.apellido);
}
}

No te preocupes si aun no entiendes completamente el tercer ejemplo, volveremos a el mas tarde en el tour.
Hagamos uso de esta idea “sobrecargando” el “constructor” de Polar.
"Una cordenada polar con una etiqueta opcional"
class Polar(angulo, radio, String? etiqueta=null) {

shared Float angulo;


shared Float radio;

shared String descripcion {


if (exists etiqueta) {
descripcion = etiqueta;
}
else {
descripcion = "(‘‘radio‘‘,‘‘angulo‘‘)";
}
}

// ...
}

Ahora podemos crear una coordenada Polar, con o sin etiqueta:


Polar orig = Polar(0.0, 0.0, "origen");
Polar coord = Polar(r,theta);

2.1.7 Aun hay mas

En el siguiente capitulo, Continuaremos investigando acerca de los atributos y especialmente atributos variable. Pero
también conoceremos conoceremos a las estructuras de control de Ceylon.

18 Capítulo 2. Clases
CAPÍTULO 3

Atributos y variables, estrucutras de control

Esta es la terera parte de este tour por Ceylon. En la pasada parada hemos aprendido acerca de clases y los conceptos
de un atributo. Una de las cosas que hace una clase especial es que puede estados-referencia a otros objetos. Asi que e
tiempo de aprender mas acerca de atributos y variables.
Tambien veremos un poco de las estructuras de control (if, switch, for, while, y try).

3.1 Atributos y valores locales

En Java, un field de una clase es facilmente distinguir entre una variable local o parametro de un constructor. Esta
distincion es menos significativa en Ceylon e frecuente irrelevante. Un atributo es realmente solo una valor declarado
en la lista de parametros o en el cuerpo de la clase, puediendo ser capturado por alguna declaración shared.
Aqui, cuenta es una variable local-block del inicializador de Contador.
class Contador () {
variable Integer cuenta=0;
}

Pero en los siguientes dos ejemplos, cuenta es un atributo.


class Contador() {
shared varible Integer cuenta=0;
}

class Contador(){
variable Integer cuenta=0;
variable Integer inc() => ++cuenta++;
}

Esto puede ser un poco confuso la primera vez, pero es realmente como el principlo de trabajo de la closure. El
mismo comportamiento aplica a valores local-block en el cuerpo de una funcion. Funciones no pueden ser declarados
miembros shared, por su puesto, pero ellos pueden retornar un objeto que capture una varaible local:
interface Contador {
shared formal Integer inc();
}

Contador crearContador () {
variable Integer cuenta=0;
object contador satisfies Contador {
shared actual Integer inc() => ++cuenta;
}

19
Un tour por Ceylon, Release

return contador;
}

O, como veremos mas adelante, una función puede retornar una funcion anidada que capture la variable local:
Integer() contador {
variable Integer cuenta=0;
Integer inc() => ++cuenta;
return inc;
}

(No te preocupes mucho acerca de la sintaxis aqui - por ahora todo lo que nos interesa es en que contador() devueve
una referencía a una función inc() la cual captura la variable cuenta.)
Asi que aunque continuemos usando el termino “valor local” y “atributo” a lo largo de nuestro viaje, manten en mente
que no hay una dinstinción significativa entre los terminos. Cualquier valor con nombre puede ser capturado por alguna
otra declaración en el mismo contenido scope. Un valor local es solo un atributo que parece no ser capturado por nadie.

3.2 Variables

Ceylon alienta a usar atributos inmutables tanto como sea posible. Un atributo inmutable obtiene su valor cuando el
objeto es inicializado, nunca mas puede ser reasignado.
class Referencia<Valor>(val){
shared Valor val;
}

value ref = Referencia("foo");


print(ref.val);
ref.val = "bar"; // compile error: value is no variable

Si nostros queremos asignar un valor a la referencia necesitamos anotarla con variable:


class Referencia<Valor>(val){
shared variable Valor val;
}

value ref = Referencia("foo");


print(ref.val);
ref.val = "bar"; // ok
print(ref.val);

3.3 Setters

Hemos conocido el concepto de un getter.


Si queremos crear un atributo con un getter mutable, necesitamos definir un matching setter. Usualmete esto unica-
mente usable si tu tienes algun otro atributo interno que quieras al que quieras establecer un valor indirectamente.
Supongamos nuestra clase tiene los siguientes atributos, diseñados para unicamente ser usados internamente (un-
shared).
variable String? nombre=null;
variable String? apellido=null;

20 Capítulo 3. Atributos y variables, estrucutras de control


Un tour por Ceylon, Release

(Recuerda que ceylon nunca incializa los atributos automaticamente a null.)


Entonces nosotros podemos abstraer los atributos usando un tercer atributo definido como un par getter/setter:
shared String nombreCompleto =>
" ".join(coalesce {nombre, apellido});

assign nombreCompleto {
value tokens = nombreCompleto.split().iterator();
if (is String primero = tokens.next()){
nombre=primero;
}
if (is String ultimo=tokens.next()){
apellido=ultimo;
}
}

Un setter es identificado por la palabra reservada assign en lugar del tipo de la declaración. (EL tipo de el matching
getter determina el tipo de el atributo.) Dentro de el cuerpo de el setter, el atributo nombre evalua al valor a ser
establecido
Asi es, se parece mucho a un par de metodos get/set en Java, aunque la sintaxis es significativamente mas simplificada.
Pero desde que los atributos en Ceylon son poliformicos, y desde que puedes redefinir una referencia como un getter
o un par getter/setter sin afectar a los clientes que llamen al atributo, no necesitas definir getter y setters amenos que
estes haciendo algo especial con el valor que estas obteniendo o estableciendo.
Nunca escribas codigo como este en Ceylon:
variable String _name = " ";
shared String name => _name; //pointless getter
assign name => _name=name; //pointless setter

No es necesario, y no obtines ningun beneficio de el.

3.4 Estructuras de control

Ceylon tiene seis estructuras de control que vienen integradas. No hay muy nuevo para los desarrolladores de Java o
de C#, asi que unos pequeños ejemplos sin muchos comentarios adicionales deberas de ser suficientes.
En primer lugar, un “gotcha” para los chicos que vienen de lenguajes similares a C: Ceylon no permite omitir las llaves
en una estructura de control. El siguiente codigo niquiera sera parseado:
if (x>100) print("grande"); //error

Se debe se escribir:
if (x>100) { print("big"); }

La razón para que las llaves no sean opcionales en Ceylon es debido a que una expresion puede comenzar con una llave
abierta, por ejemplo, {“hola”, “mundo” }, así que llaves opcionales en estructuras de control harán que la gramática
sea ambigua a el parser.)
Ok, ahora es momento de ir a los ejemplos.

3.4.1 if

La declaración del if/else es completamente tradicional:

3.4. Estructuras de control 21


Un tour por Ceylon, Release

if (x > 1000) {
print("Realmente grande");
}
else if (x > 100) {
print("Grande");
}
else {
print("pequeño");
}

Después aprenderemos como el if puede estrechar el tipo de referencia en su bloque. Ya hemos visto un ejemplo de
esto, pero volveremos a hablar de esto con cuando lleguemos a tipos opcionales.
Nosotros usamos frecuentemente el operador then e else en vez de if.

3.4.2 switch

La declaración switch/case elimina el muy criticado comportamiento “fall through” y la sintaxis irregular:
switch (x<=>100)
case (pequenio) { print("pequeño"); }
case (igual) { print("Cien"); }
case (grande) { print("Grande"); }

EL tipo de la expresión que evalua switch deberá de ser un tipo enumerated. Tu no puedes usar switch con un String
o un Integer. (Utiliza if en vez de ello.)
Aun tenemos mucho mas que decir acerca de switch cuando discutamos tipos enumerated.

3.4.3 assert

Ceylon también tiene una declaración assert:


assert (longitud < 10);

Los assert son buenos para crear declaraciones donde tu conoces que tiene que ser verdadero, pero no son aparentes a
otros lectores del código(incluyendo el identificador de tipos!). Los usos comunes de un assert incluyen cosas como
precondiciones, postcondiciones y clases invarientes.
Si la condición es false es lanzada una excepciones en tiempo de ejecución. El mensaje de la excepción ayudara incluira
detalles de la condición que fue violada, esto es muy importante cuando un assert tiene mas de una condicional.
assert (exists arg, !arg.empty);

Para personalizar el mensaje de assert, agrega una anotación doc:


"La longitud debera ser de almenos 10"
assert (longitud < 10)

En su caso, el analizador de tipos usara assert información para los tipos cuando checa declaraciones que siguen al
assert, por ejemplo:
Integer? x = parseInteger("1");
assert (exists x);
// Despues de ‘assert‘, x tiene el tipo Integer enve de Integer?
value y = x+10;

22 Capítulo 3. Atributos y variables, estrucutras de control


Un tour por Ceylon, Release

Esto es realmente el mismo comportamiento que hemos visto anteriormente, unicamente esta vez enmedio de un
bloque en vez de al inicio de un bloque if. (No te preocupes, habrá más de estos ejemplos después.)
Nótese que, a diferencia del assert de Java, que puede ser deshabilitado en tiempo de ejecución, los assert en Ceylon
siempre estarán habilitados.

3.4.4 for

EL ciclo for tiene un bloque opcional else, que puede ser ejecutado cuando el ciclo termina completamente sus itera-
ciones si pasar por una declaración return o un break.
variable Boolean menores;

for (p in gente) {
if (p.age<18) {
menores=true;
break;
}
}
else {
menores=false;
}

Este no es un for estilo C. En vez de ello, puedes usar el operador de rango longitudinal :, para producir una secuencia
de Integer, dando su inicio y su longitud:
for (i in min:lon) { ... }

Alternativamente, puede usar el operador de rango común .. para producir una secuencia de Integer entre dos puntos:
for (i in min..max) { ... }

Existen algunos otros trucos con un for que veremos mas adelante.
Nosotros usamos comprehensions o incluso funciones higher order en vez de for.

3.4.5 while

EL while puede se usado de la forma tradicional.


value it = nombres.iterator();
while (is String siguiente = it.next()){
print(siguiente);
}

No hay una declaración do/while.

3.4.6 try

La declaración try/catch/finally trabajan como en Java:


try {
message.send();
}
catch (ConnectionException|MessageException e) {
tx.setRollbackOnly();
}

3.4. Estructuras de control 23


Un tour por Ceylon, Release

Para manejar todas la excepciones de Ceylon, junto con todas las excepciones de JavaScript, o todas las excepcio-
nes que son subclases de java.lang.Exception, , podemos catch el tipo Exception definido en ceylon.language. Si no
especificamos explicitamente un tipo, Exception es inferido:
try {
message.send();
}
catch (e) {// Equivalente a "catch (Exception e)"
tx.setRollbackOnly();
}

No hay una manera de manejar excepciones de tipo java.lang.Error.


Eventualmente try soportara expresiones resource similares a las Java 7.
try (Transaction()) {
try(s = Session()){
s.persist(person);
}
}

Notas de la implementación Milestone 5


Expresiones Resource aun no son implementados.

3.4.7 Condition list

Construcciones como if,while y assert aceptan Contion list. Una condition list es una lista no ordenada de múlti-
ples booleanos, exists, nonempty e is. La ‘condition list es satisfecha si (y solo si) cada una de las condicionales es
satisfecha.
Con condicionales booleanas puedes lograr el mismo comportamiento con el operador &&. Pero con condition list te
permite usar el “structured typecasting” de exists, is y amigos en condiciones que aparezcan después en la misma lista.
Veamos un ejemplo usando assert:
value url = parseUri("http://ceylon-lang.org/download");
assert(exists authority=url.authority,
exists host=authority.hostname);
// Hacer algo con host

Aqui puedes ver dos condiciones exists en la declaración assert, separadas con coma. La primera declara authority‘(que
es inferida a ser una ‘String en vez de una String? debido al exists). La segunda condición entonces usa su propia exists
condición.
Lo importante a notar es que el compilador nos permite usar authority en la segunda condición y conocer que es una
String y no una String?. No puedes hacer esto con múltiples “&&” condiciones. Puedes lograrlo anidando varios if,
pero hará menos legible el código y no trabajara bien en un declaración while o somprehension.

3.4.8 Aun hay mas...

Ahora que conocemos acerca de las clases y sus miembros, estamos listos para explorar herencia y refinamien-
to(overriding).

24 Capítulo 3. Atributos y variables, estrucutras de control


CAPÍTULO 4

Herencia, refinamiento, e interfaces

Esta es la cuarta parada de el Tour de Ceylon. En la parte anterior hemos visto atributos, variables, setters, y estructuras
de control. En esta sección aprenderemos acerca de Herencia y refinamiento(conocido como “overriding” en muchos
lenguajes).
La herencia es uno de dos caminos que Ceylon nos permite para abstraer los tipos. (La otra son generics, los cuales
veremos mas adelante en este tour.) Una característica de Ceylon es un tipo de herencia múltiple llamada “mixin
inheritance”. Tal vez has escuchado o experimentado la herencia múltiple en C++. Pero mixini inheritane en Ceylon
mantiene una seria de restricciones que mantienen un buen balance entre poder y inocuidad.

4.1 Herencia y refinamiento

En programación orientada a objetos, frecuentemente remplazamos condicionales (if, switch) con subtipos. De hecho,
acuerdo a algunas personas, esto es lo que hace un programa orientado a objetos. Tratemos de re-factorizar la clase
Polar de la anterior parada del tour en dos clases, con dos diferentes implementaciones de description. Aquí esta la
super clase:
"Una coordenada polar"
class Polar(Float angulo, Float radio) {

shared Polar rotar(Float rotacion) =>


Polar(angulo+rotacion, radio);

shared Polar dilatar(Float dilatacion) =>


Polar(angulo, radio*dilatacion);

"La descripcion por default"


shared default String descripcion =>
"(‘‘radio‘‘,‘‘angulo‘‘)";
}

Notese que Ceylon fuerza a que declaremos atributos o metodos que pueden ser refinados(overriden) anotándolos con
default.
Subclases especifican su super-clase usando la parabra reservada extends, seguida por el nombre de la super-clase,
seguida por una lista de argumentos a ser enviados al inicializador de la super-clase. Parece solo una expresión que
instancia una super-clase:
"Una coordenada polar con una etiqueta"
class EtiquetaPolar(Float angulo, Float radio, String etiqueta)
extends Polar(angulo, radio) {

25
Un tour por Ceylon, Release

"La descripcion con etiqueta"


shared actual String descripcion =>
etiqueta + "-" + super.descripcion;
}

Ceylon también nos fuerza a declarar que atributo o método(override) un atributo o método de una super-clase anotán-
dola con actual (no override en Java). Todas estas anotaciones tienen un pequeño costo extra al teclear, pero ayudan
al compilador a detectar errores. No podemos inadvertidamente refina un miembros de una super-clase, o inadvertida-
mente fallar al refinarla.
Note que Ceylon deja fuera este camino al repudiar la idea del “duck” tipyng o structural typing. Si el camina() como
un Pato, entonces el deberá de ser un subtipo de Duck y deberá de refinar explícitamente la definición de caminar() en
Pato. Nosotros no creemos que solamente el nombre de un método o atributo es suficiente para identificar su semántica.
Y mas importante, “structural typing” no trabaja adecuadamente con herramientas.

4.1.1 Sintaxis corta para refinamiento

Existe una manera mas compacta para refinar un miembro default de una super-clase simplemente especificando su
refinada implementación usando =>, como en el siguiente ejemplo:
"Una coordenada polar con una etiqueta"
class EtiquetaPolar(Float angulo, Float radio, String etiqueta)
extends Polar(angulo, radio) {

descripcion => etiqueta + ": " + super.descripcion;


}

O asignado un valor usando =, como el siguiente ejemplo:


"Una coordenada polar con una etiqueta"
class EtiquetaPolar(Float angulo, Float radio, String etiqueta)
extends Polar(angulo, radio) {

descripcion = "‘‘etiqueta‘‘: (‘‘radio‘‘, ‘‘angulo‘‘)";


}

Puedes refinar cualquier función o cualquiera que no sea variable usando esta sintaxis.

4.1.2 Refinando un miembro de Object

Nuestra clase Polar es un subtipo implicito de la clase Object en el paquete ceylon.language. Si quieres mirar dentro
de esta clase, podrás ver que tiene un atributo default llamado string. Es común refinar este atributo para proveer a un
desarrollador una amigable reprensentación del objeto.
Nuestra clase Polar es también un subtipo de la interfaz Identifiable la cual define implementaciones default de equals()
y hash(). Tambien necesitamos refinar estos:
"Una coordenada polar"
class Polar(Float angulo, Float radio) {

// ...

shared default String descripcion =>


"(‘‘radio‘‘,‘‘angulo‘‘)";

26 Capítulo 4. Herencia, refinamiento, e interfaces


Un tour por Ceylon, Release

value azimuth => pi * (angulo/pi).fractionalPart;

shared actual Boolean equals(Object that){


if (is Polar that) {
return azimuth==that.azimuth &&
radio==that.radio;
}
else {
return false;
}
}

shared actual Integer hash => radio.hash;

shared actual String string => description;


}

Esta es la primera vez que hemos visto esta sintaxis:


if (is Polar that) { ... }

Como probablemente supusiste, if (is ...) trabaja como if (exists ...), probando y estrechando el tipo de un valor. En este
caso prueba el tipo de that y lo estrecha a Polar si that es una instancia de Polar. Volveremos después en el tour a esta
construcción.
Usando la sintaxis corta para refinamiento que hemos visto, podemos abreviar el código anterior como esto:
"Una coordenada polar"
class Polar(Float angulo, Float radio) {

// ...

shared default String descripcion =>


"(‘‘radio‘‘,‘‘angulo‘‘)";

value azimuth => pi * (angulo/pi).fractionalPart;

shared actual Boolean equals(Object that){


if (is Polar that) {
return azimuth==that.azimuth &&
radio==that.radio;
}
else {
return false;
}
}

hash => radio.hash;

string => descripcion;


}

(Pero en este caso, esta sintaxis corta es quizás no una mejora.)

4.1. Herencia y refinamiento 27


Un tour por Ceylon, Release

4.1.3 Clases abstractas

Ahora consideremos un problemas mas interesante: Una abstracción sobre el sistema de coordenadas polar y cartesia-
na. Desde que una coordenada no es solo un tipo especial de coordenadas polares, esto es un caso para la introducción
de una super-clase abstracta:
"Una libre abstraccion de un sistema de coordenadas para un punto
geometrico"
abstract class Point() {

shared formal Polar polar;


shared formal Cartesiano cartesiano;

shared formal Punto rotar(Float rotacion);


shared formal Punto dilatar(Float dilatacion);

Ceylon requiere que anotemos las clases abstractas con abstract, al igual que en Java. Esta anotación indica que una
clase no puede ser instanciada y puede definir miembros abstractos. Como en Java, Ceylon también requiere que
anotemos con abstract los miembros a los cuales no especifiquemos una implementación. Sin embargo, la anotación
requerida en formal. La razón para tener dos distintas anotaciones, como veremos mas adelante, es que clases anidadas
pueden ser abstract o formal, y las clases anidadas abstract son un poco diferentes a una clase miembro formal. Una
clase miembro formal puede ser instanciada; una clase abstract no puede.
Note que un atributo que nunca es inicializado es siempre un atributo formal. Ceylon Ceylon no inicializa los atributos
a cero o null amenos que tu lo especifiques.
Una manera de definir una implementación para una atributo abstracto heredado es usar la sintaxis corta de refina-
miento que hemos visto anteriormente.
"Una coordenada polar"
class Polar(Float angulo, Float radio)
extends Punto() {

polar => this;

cartesiano => Cartesiano(radio*cos(angulo), radio*sin(angulo));

rotar(Float rotacion) => Polar(angulo+rotacion, radio);

dilatar(Float dilatacion) => Polar(angulo, radio*dilatacion);

Alternativamente, podemos escribirlo de la manera larga.


"Una coordenada polar"
class Polar(Float angulo, Float radio)
extends Punto() {

shared actual Polar polar => this;

shared actual Cartesiano cartesiano =>


Cartesiano(radio*cos(angulo), radio*sin(angulo));

shared actual Polar rotar(Float rotacion) =>


Polar(angulo+rotacion, radio);

28 Capítulo 4. Herencia, refinamiento, e interfaces


Un tour por Ceylon, Release

shared actual Polar dilatar(Float dilatacion) =>


Polar(angulo, radio*dilatacion);

Note que Ceylon, como Java, permite el refinamiento co-variante del tipo de un miembro. Refinamos el tipo de retorno
de rotar() y dilatar, estrechando a Polar desde el tipo mas general declarado por Punto. Pero Ceylon actualmente no
soporta refinamiento contra variante de los tipos de un parámetro. No puedes refinar un método y ensanchar el tipo de
un parámetro. (Algún día nos gustaría arreglar esto.)
Por supuesto, no puedes refinar un miembro y ensanchar el tipo de retorno, o cambiar a algún tipo arbitrario diferente,
desde que en dicho caso la subclase deberá no ser a lo largo un subtipo de el supertipo. Si tu vas a refinar el tipo de
retorno, tienes que refinarlo a un subtipo.
Cartesiano también de mannera covariante refina rotar() y dilatar(), pero a un tipo diferente de retorno.
import ceylon.math.float { atan }

"Una coordenadas cartesiana"


class Cartesiano(Float x, Float y)
extends Punto() {

shared actual Polar polar => Polar( (x^2+y^2)^0.5, atan(y/x) );

shared actual Cartesiano cartesiano => this;

shared actual Cartesiano rotar(Float rotacion) =>


polar.rotar(rotacion).cartesiano;

shared actual Cartesiano dilatar(Float dilatacion) =>


Cartesiano(x*dilatacion, y*dilatacion);

No hay una manera de prevenir que otro código extienda una clase (no hay un equivalente una clase final in Java).
Desde que únicamente miembros que son declarados para soportar refinamiento usando alguno de formal o default
puede ser refinado, un subtipo no puede romper la implementación de un supertipo. A menos que el supertipo fuese
explícitamente diseñado a ser extendido, un subtipo puede agregar métodos, pero nunca cambiar el comportamiento
de los miembros heredados.
Las clases abstractas son útiles. Pero desde que las interfaces en Ceylon son mas poderosas que las interfaces en Java,
es mas frecuente usar una interfaz en vez de una clase abstracta.

4.2 Interfaces y herencia “múltiple”

Algunas veces, nos surge alguna situación donde una clase necesita de funcionalidades anidadas de mas de un su-
pertipo. El modelo de herencia de Java no soporta esto, desde que una interfaz no puede define un miembro con una
implementación concreta. Las interfaces en Ceylon son un poco más flexibles:
Una interfaz puede definir un método concreto, getter y setters de atributos, pero
Puede no definir referencia o inicialización lógica.
Note que prohibiendo las referencias y la inicialización lógica crea interfaces completamente sin estado. Una interfaz
no puede mantener referencia a otros objetos.
Tomemos ventaja de herencia “mixin” para definir una interfaz Writer para Ceylon.

4.2. Interfaces y herencia “múltiple” 29


Un tour por Ceylon, Release

interface Writer {

shared formal Formatter formatter;

shared formal void write(String string);

shared void writeLine(String string) {


write(string);
write("\n");
}

shared void writeFormattedLine(String format, Object* args) {


writeLine(formatter.format(format, args));
}
}

Note que no podemos definir un valor concreto para el atributo formatter, desde que una interfaz no puede mantener
una referencia a otro objeto.
Ahora definamos una concreta implementación de la interfaz.
class ConsoleWriter() satifies Writer {
formatter => StringFormatter();
write(String string) => print(string);
}

La palabra reservada satisfies (no implements como en Java) es usada para especificar que una interfaz extiende a
otra interfaz o que una clase implementa una intefaz. A diferencia de la declaración extends, una declaración satisfies
no se le especifican argumentos , puesto que las interfaces no tienen parámetros o inicialización logica. Ademas, la
declaración satisfies puede declarar mas de una interfaz.
El enfoque de Ceylon en las interfaces elimina un patrón común en Java donde una clase abstracta separada define una
implementación por default para algún miembro de la interfaz. En Ceylon, la implementación por default puede ser
especificada por la interfaz misma. Incluso mejor, es posible agregar un nuevo miembro a una interfaz sin romper una
implementación existente de la interfaz.

4.2.1 Ambigüedad en herencia “múltiple”

Es ilegal para un tipo heredar dos miembros con el mismo nombre, amenos que ambos miembros(directa o indirec-
tamente) refinen a un común miembros de un común supertipo, y el tipo heredado también refine el miembro para
eliminar cualquier ambigüedad. Lo siguiente resulta en un error de compilación:
interface Party {
shared formal String legalName;
shared default String nombre => legalName;
}

interface User {
shared formal String userId;
shared default String name => userId;
}

class Customer(String customerName, String email)


satisfies User & Party {

shared actual String legalName = customerName;


shared actual String userId = email;

30 Capítulo 4. Herencia, refinamiento, e interfaces


Un tour por Ceylon, Release

shared actual String name = customerName; // error: refina dos diferentes


// miembros
}

Para arreglar este código, deberá factorizar una declaración formal de el atributo name a un común supertipo. El
siguiente código es legal.
interface Named {
shared formal String name;
}

interface Party satisfies Named {


shared formal String legalName;
shared actual default String name => legalName;
}

interface User satisfies Named {


shared formal String userId;
shared formal default String name => userId;
}

class Customer(String customerName, String email)


satisfies User & Party {
shared actual String legalName = customerName;
shared actual String userId = email;
shared actual String name = customerName;
}

Oh, y por supuesto lo siguiente es ilegal:


interface Named {
shared formal String name;
}

interface Party satisfies Named {


shared formal String legalName;
shared actual String name => legalName;
}

interface User satisfies Named {


shared formal String userId;
shared formal String name => userId;
}

class Customer(String customerName, String email)


satisfies User & Party { // error: inherits multiple definitions
// of name
shared actual String legalName = customerName;
shared actual String userId = email;
}

Para arreglar este código, name deberá de ser declarado default en ambos User y Party y explícitamente refinado en
Customer.

Aun hay mas

En la siguiente parada, para finalizar la discusión de programación orientada a objetos en Ceylon, aprenderemos acerca
de clases anónimas y clases miembro.

4.2. Interfaces y herencia “múltiple” 31


Un tour por Ceylon, Release

32 Capítulo 4. Herencia, refinamiento, e interfaces


CAPÍTULO 5

Clases anónimas y miembro

Esta es la cuarta parada en el tour de Ceylon. En la parada anterior hemos aprendido acerca de herencia y refinamientos.
Es momento de completar nuestra introducción a la programación orientada a objetos en Ceylon aprendiendo acerca
de las clases anónimas y clases miembro.

5.1 Clases anónimas

Si una clase no tiene parámetros, es posible usar una declaración corta que defina una instancia con nombre de la clase,
sin proveer un nombre para la clase misma. Esto es usualmente de mas ayuda cuando estamos extendiendo una clases
abstracta o implementando una interfaz.
doc "El origen"
object origen extends Polar(0.0, 0.0) {
descripcion => "origen";
}

Una clase anónima puede extender una clase ordinaria y satisfacer una interfaz.
shared object consoleWriter satisfies Writer {
formatter = StringFormatter();
write(String string) => process.write(string);
}

La desventaja de la declaración de un objeto es que no podemos escribir código que refiera a el tipo concreto de origen
o consoleWriter, únicamente a la instancia con nombre.
Tal vez estés tentado a pensar a la declaración de objetos como definición de singletons, pero eso no es del todo
correcto.
La declaración de un objeto en toplevel, de hecho define un singleton.
La declaración de un objeto anidado en una clase define un objeto por cada instancia de la clase contenedora.
La declaración anidada en un método, getter, o setter da como resultado un nuevo objeto cada vez que el método,
getter o setter es ejecutado.
Veamos como esto puede ser útil:
interface Subscription {
shared formal void cancel();
}

Subscription register(Suscriber s) {

33
Un tour por Ceylon, Release

subscribers.append(s);
object subscription satisfies Subscription {
shared actual void cancel() =>
subscribers.remove(s);
}
return subscription;
}

Note como este código de ejemplo hace hábilmente uso de el hecho que la declaración anidada recibe una closure de
los valores contenidos en la declaración del método contenedor.
Una manera distinta de entender acerca de la diferencia de un objeto y una clase es pensar que una clase es como un
object con parámetros. (Por supuesto hay una gran diferencia: una declaración de una clase define un tipo con nombre
y con esto podemos referirnos a el en otras partes del programa.) Como veremos después, Ceylon nos permite pensar
en los métodos como atributos con parámetros.
Un declaración object puede refinar un atributo declarado con formal o default mientras este sea un suptipo del tipo
declarado en el atributo a refinar.
shared abstract class App() {
shared formal OutputStream stream;
...
}

class ConsoleApp() extends App() {


shared actual object stream
satisfies OutputStream { ... }
...
}

Sin embargo, un object no podra ser mismo declarado formal o default.

5.2 Clases miembro y su refinamiento

Probablemente has usado anidar una clase dentro de un método o clase. Desde que Ceylon es un lenguaje con bloques
de estructura recursivos, la idea de una clase anidada es mas que natural. Pero en Ceylon, una clase anidada no abstracta
es un miembro del tipo contenedor. Por ejemplo, BufferReader define la clase miembro Buffer:
class BufferReader(Reader reader)
satifies Reader {

shared default class Buffer()


satisfies List<Character> { ... }

...

La clase miembro buffer es anotada con shared, entonces podemos instanciar la clase de la siguiente manera:
BufferReader br = BufferReader(ExampleReader());
BufferReader.buffer b = br.Buffer();

Note que el tipo anidada deberá ser identificado junto con el tipo contenedor cuando es usada fuera de la clase.
El miembro de la clase Buffer es también anotado con‘default‘, así que podemos refinarlo en un subtipo de BufferRea-
der:

34 Capítulo 5. Clases anónimas y miembro


Un tour por Ceylon, Release

class BufferFileReader(File file)


extends BufferReader(FileReader(file)) {
shared actual class Buffer()
extends super.Buffer() { ... }
...
}

Es correcto: ¡Ceylon nos permite “sobreescribir” una clase miembro de un supertipo!


Note que BufferFileReader.Buffer es una subclase de BufferReader.Buffer.
Ahora, la instancia anterior br.buffer(), !es una operación polimorfa¡ Puede devolver una instancia de BufferRea-
der.Buffer o de BufferReader.buffer, dependiendo al que refiera br de BufferReader o BufferFileReader. Esto es mas
que un lindo truco. La instanciación nos permite eliminar al concepto llamado “factory method pattern” de nuestro
código.
Es posible incluso, definir una clase miembro formal. Una clase miembro formal puede declarar miembros formal.
abstract class BufferReader (Reader reader)
satisfies Reader {
shared formal lass Buffer() {
shared formal Byte read();
}
...
}

En este caso, una subclase concreta de la clase abstract deberá refinar los miembros formal de la clase.
shared class BufferFileReader(File file)
extends BufferReader(FileReader(file)) {
shared actual class Buffer()
extends super.Buffer() {
shared actual Byte read() {
...
}
}
...
}

Nótese la diferencia entre una clase abstract y una clase miembro formal. Una clase anidada abstract y no necesita ser
refinada por subclases concretas de la clase contenedora. Una clase miembro formal puede ser instanciada y deberá
ser refinada por cada subclase de la clase contenedora.
Es un interesante ejercicio comparar el refinamiento de las clase miembro en Ceylon con la funcionalidad de inyección
de dependencias en los frameworks con Java. Ambos mecanismos proveen un significado de abstraer la operación de
instanciación de un tipo. Puedes pensar que las subclases que refinan un miembro tipo como llenando el mismo rol
como una configuración de dependencia en un framework de inyección de dependencias.

5.2.1 Aun hay mas

Clases miembro y su refinamiento permiten a Ceylon soportar type families. No hablaremos de ello en este tour.
En la siguiente estación, conoceremos a las secuencias, basandose Ceylon en el tipo “Array”.

5.2. Clases miembro y su refinamiento 35


Un tour por Ceylon, Release

36 Capítulo 5. Clases anónimas y miembro


CAPÍTULO 6

Iterables, secuencias y tuplas

Esta es la sexta parada de el tour de Ceylon. En la parada anterior cubrimos las clases anónimas y las clases miembro.
Ahora veremos acerca de los objetos iterables, las secuencias y las tuplas. Estos son ejemplos de objetos contenedores
genéricos. No te preocupes hablaremos mas de los objetos genéricos mas adelante.

6.1 Iterables

Un objeto iterable es un objeto que produce un flujo de valores. Los objetos iterables satisfacen la interfaz Iterable.
Ceylon provee algo de azúcar sintáctica para trabajar con objetos iterables.
El tipo Iterable<X,Null> representa un objeto iterables que tal vez no genere algún valor cuando es iterado, y
puede ser abreviado {X*}, y
El tipo Iterable<X,Nothing> representa un objeto iterables que siempre produce al menos un valor cuando es
iterado, y es usualmente abreviado {X+}.
También podemos construir una instancia de Iterable usando llaves:
{String+} palabras = { "hola", "mundo" };
{String+} masPalabras = { "hello", "world", *palabras };

El prefijo * es llamado spread operator. El “extiende” los valores de un objeto iterable. Así masPalabras produce los
valores “hello”, “world”, “hola”, “mundo” cuando es iterado.
Como veremos después, las llaves pueden incluso contener comprenhension, haciéndolo mucho mas poderoso que lo
que hemos visto.
Iterable es un subtipo de la interfaz Category así es que podemos usar el operador in para probar si el valor es producido
por un Iterable.
if (exists char = text[i],
char in {’,’, ’.’, ’!’, ’?’, ’;’, ’:’}) {
//...
}

"index debera estar entre 1 y 100"


assert (index in 1..100);

El operador in‘es solo azúcar sintáctica para el método ‘contains de Category‘.

37
Un tour por Ceylon, Release

6.1.1 Iterando con for

Para iterar una instancia de Iterable, podemos usar un ciclo for:


for (palabra in masPalabras) {
print(palabra);
}

Si, por alguna razón, necesitamos un indice para cada elemento producido por por un objeto iterable, podemos usar
una variación espacial de el ciclo for que ha sido diseñado para iterar Entry:
for (i -> palabra in entries(masPalabras)) {
print("‘‘i‘‘: ‘‘palabra‘‘");
}

La función entry devuelve una instancia de Entry<Integer,String>‘[] conteniendo los elementos indexados de la se-
cuencias. (La ‘-> es azúcar sintáctica para la clase Entry.)
Esto frecuentemente útil para iterar dos secuencias a la vez. La función zip() es practico en el siguiente caso:
for (nombre -> lugar in zip(nombre,lugar)) {
print(nombre + " @ " + "lugar");
}

6.2 Secuencias

Algunos tipos de array o listas es una característica universal de todos los lenguajes de programación. El modulo del
lenguage de Ceylon define soporte para tipo secuencia a través de las interfaces Sequential, Sequence y Empty.
De nuevo, hay mas azúcar sintáctica asociada con secuencias:
El tipo Sequential<X> representa una secuencia que puede puede ser vacía y puede ser abreviada [X*] o X[],
El tipo Sequence<X> representa una secuencia no vacia y puede ser abreviada [X+],
El tipo Empty representa una secuencia vacía y es abreviada [].
Algunos operaciones de el tipo Sequence no son definida por Sequential, asi que no llamaras estas si lo que tienes es
X[]. Sin embargo, necesitamos la construcción if (nonempty ...) para tener acceso a estas operaciones.
void printBound(String[] strings) {
if (nonempty strings) {
//strings es de tipo [String+] en este bloque
print(strings.first + ".." + strings.last);
}
else {
print("Empty");
}
}

Note como este es solo la continuación de el patrón establecido para el manejo de null. De hecho, ambas construcciones
son solo abreviaciones para la reducción de tipos:
if (nonempty strings) es una abrecian para if (is [String+] strings), al igual que
if (exists name) es una abreviación para para if (is Object name).

38 Capítulo 6. Iterables, secuencias y tuplas


Un tour por Ceylon, Release

6.2.1 Azúcar sintáctica en secuencias

Hay mucho mas azúcar sintáctica para secuencias. Podemos utilizar un grupo de sintaxis de Java:
String[] operators = [ "+", "-", "*", "/" ];
String? plus = operators[0];
String[] multiplicative = operators[2..3];

Oh, y la expresion [] evalua a una instancia de Empty.


Sin embargo, a diferencia de Java, todas estas construcciones son solo abreviaciones. El código anterior es exactamente
equivalente a el siguiente código sin azucarado:
Sequential<String> operators = ...;
Null|String plus = operators.get(0);
Sequential<String> multiplicative = operators.span(2,3);

(Volveremos en unos minuto para ver que significa una lista de valores dentro de corchetes.)
La interfaz Sequence extiende a Iterable, así que podemos iterar Sequence usando un ciclo for:
for (op in operators) {
print(op);
}

6.3 Rangos

Un range es un tipo de Sequence. El siguiente codigo:


Character[] uppercaseLetters = ’A’..’Z’;
Intergers[] countDown = 10..0;

Es solo azúcar para:


Sequential<Character> uppercaseLetters = Range(’A’,’Z’);
Sequential<Integer> countDown = Range(10,0);

De hecho, esto es solo una pequeña vista de el hecho que case todos los operadores son solo azúcar para llamar a
métodos a un tipo. Volveremos a esto mas adelante, cuando hablemos acerca de polimorfismo en operadores.
Ceylon no necesita un for al estilo de C. En vez de ello, combina un for con un operador de rango.
variable Integer fac=1;
for (n in 1..100) {
fac*=n;
print("Factorial ‘‘n‘‘! = ‘‘fac‘‘");
}

6.3.1 Secuencias y sus supertipos

Es probablemente un buen momento para ver código mas avanzado en Ceylon. Que mejor lugar para encontrar que en
modulo del lenguaje mismo.
Puedes encontrar le documentación de la API y su código de sequence en linea, o puedes ir directamente a Navigate
> Open Ceylon Declaration...” para ver la declaración de ‘Sequential directamente en la IDE de Ceylon.
Las operaciones mas importantes de Sequential son heredadas de Correspondence e Iterable.

6.3. Rangos 39
Un tour por Ceylon, Release

Correspondence provee la capacidad de acceder a elementos por medio de indices, y


Iterables provee la habilidad de iterar los elementos de una secuencia.
Ahora abre la clase Range en el IDE, para ver una implementación concreta de la interfaz Sequence.

6.3.2 Secuencias vacías y el tipo de fondo

Finalmente, revisemos la definición de Empty, Note que Empty es declarado como un subtipo de List<Nothing>. Este
tipo especial Nothing, es llamado el tipo de fondo, representa:
El conjunto vació, o equivalentemente
La intersección de todos los tipos.
Desde que el conjunto vació es un subconjunto de todos los otros conjuntos, Nothing es asignable a todos los otros
tipos. ¿Por que esto es útil aquí? Bueno, Correspondence<Integer,Element> e Iterable<Element> son ambos cova-
riantes en el parámetro de tipo Element. Así Empty es asignable a Correspondence<Integer,T> y Iterable<T> para
cualquier tipo T. Esto es el por que no se necesita un tipo de parámetro.
Desde que no hay instancias actuales de Nothing,si alguna vez ves un atributo o método de tipo Nothing, tendrás la
certeza que no es posible que revuelva un valor. Esto es un camino posible para que tal operación termine con una
excepción.
Otra cosa a notar es el valor de retorno de first e item() de Empty. Tal vez hallas estado esperando ver Nothing? aquí,
desde que ellos sobreescriben los miembros del supertipo de tipo T?. Pero como hemos visto en la primera parte del
tour, Nothing? es solo una abreviación para Null|Nothing. Y Nothing es el conjunto vació, así la unión Nothing|T de
Nothing con algún otro tipo T es solo T.
El compilador de Ceylon esta habilitado para hacer todo este razonamiento automáticamente. Así cuando el vea un
Iterable<Nothing>, el sabe que la operación first es de tipo Null, por ejemplo, que su valor es null.
Cool, ¿no?

6.3.3 Gotchas para Desarrolladores de Java

Superficialmente, un tipo secuencia luce cono un array de Java, ¡pero realmente es muy muy diferente! Primero, por
supuesto, un tipo secuencia Sequential<String> es una interfaz inmutable, este no es un tipo concreto mutable como
un array. No podemos establecer un valor de un elemento:
String[] operators = ....;
operators[0] = "‘"; //compile error

Ademas, la operación indice operators[i] devuelve un tipo opcional String?, que resulta en código un poco diferente
al idioma. Para comenzar, no iteramos secuencias por indice como en C o Java. El siguiente código no compila:
for (i in 0..operators.size-1){
String op = operators[i]; // compile error
}

Aquí, operators[i] es de tipo ‘String? que no es directamente asignable a String.


En vez de ellos, si necesitamos acceder a el indice, usaremos la forma especial de for mostrada anteriormente.
for (i -> op in entries(operators)) {
//...
}

Así mismo, frecuentemente no hacemos una prueba adelantada de un indice contra la longitud de la secuencia.

40 Capítulo 6. Iterables, secuencias y tuplas


Un tour por Ceylon, Release

if (i>operators.size-1) {
throw IndexOutBoundException();
}
else {
return operators[i]; //compile error
}

En su lugar, hacemos la prueba después de acceder al elemento de la secuencia:


if (exists op = operators[i]) {
return op;
}
else {
throw IndexOutBoundException();
}

De hecho este es un uso común para assert:


assert(exists op = operators[i]);
return op;

Especialmente nunca necesitaremos escribir lo siguiente:


if (i>operators.size-1) {
return "";
}
else {
return operators[i]; //compile error
}

Es mucho mas limpio y elegante:


return operators[i] else "";

Todo esto puede que tome algo de tiempo empezar a usarlo. Pero es agradable que este mismo idioma aplique a otros
tipos de Correspondence. incuyendo Map.

6.4 Tuplas

Tuplas es una lista ligada que captura el tipo estático para cada elemento individual en la lista. Por ejemplo:
[Float, Float, String] point = [0.0, 0.0, "origin"];

Esta tupla contiene a dos Float seguidos por una String. Esta información es capturada en su tipo estático, [Float,
Float, String].
Cada liga de la lista es una instancia de la clase Tuple. Si realmente deseas conocer, el código anterior es solo azúcar
sintáctica para el siguiente código:
Tuple<Float|String,Float,Tuple<Float|String,Float,Tuple<String,String>>>
punto = Tuple(0.0, Tuple(0.0, Tuple("origin", [])));

Sin embargo, siempre usaremos la azúcar sintáctica cuando trabajemos con tuplas.
Tuple extiende Sequence, así que podemos hacer todas las cosas usuales con secuencias con las tuplas. Como con una
secuencia, podemos acceder a un elemento de la tupla por indice. Pero en el caso de una tupla, Ceylon esta habilitado
para determinar el tipo de el elemento cuando es indexado por un entero literal.

6.4. Tuplas 41
Un tour por Ceylon, Release

Float x = point[0];
Float y = point[1];
Float label = point[2];
Null zippo = point[3];

Una tupla no terminada(unterminated) es cuando la ultima liga en la lista es una secuencia, no un Empty. Por ejemplo:
String[] labels = ...;
[Float, Float, String*] point = [0.0, 0.0, \*labels];

Esta tupla contiene dos Float seguido por un numero no conocido de String.
Ahora podemos ver que un tipo secuencia como [String*] o [String+] puede ser vista como un tipo de tupla degene-
rada.

6.4.1 Aun hay mas

Si estas interesado, puedes encontrar una discusión mas en profundidad de tuplas aqui.
En adelante vamos a explorar algunos detalles mas de el sistema de tipos, comenzando con tipos alias e inferencia de
tipos.

42 Capítulo 6. Iterables, secuencias y tuplas


CAPÍTULO 7

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.

7.1 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>

43
Un tour por Ceylon, Release

7.2 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.

7.2.1 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;

44 Capítulo 7. Alias e inferencia de tipos


Un tour por Ceylon, Release

¿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.

7.2.2 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 super-
tipos 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+}

7.2.3 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.

7.2. Inferencia de tipos 45


Un tour por Ceylon, Release

46 Capítulo 7. Alias e inferencia de tipos


CAPÍTULO 8

Unión, intersección y tipos enumerados

Este es la octava parada en el tour de Ceylon. En la parada anterior aprendimos acerca de los alias y la inferencia de
tipos. Continuemos explorando el sistema de tipos de Ceylon.
En este capitulo, vamos a discutir los cercanos tópicos de tipos unión e intersección y los tipos enumerados. En esta
área, el sistema de tipos de Ceylon trabaja un poco distinto a otros lenguajes de tipos estáticos.

8.1 Reduciendo el tipo de un objeto referencia

En cualquier lenguaje con subtipos hay ocasiones en las que necesitas realizar reducción con conversiones. En dema-
siados lenguajes de tipos estáticos, esta es la segunda parte del proceso. Por ejemplo, en Java primero probamos el tipo
del objeto usando el operador instanceof, y entonces intentar reducirlo usando un typecast estilo C. Esto es un poco
curioso, desde que estos no son virtualmente buenos usos para instanceof que no involucra una conversión inmediata
al tipo que fue probado, y el cambio de tipo sin probar el tipo es peligrosamente un tipo no seguro.
Como puedes imaginar, Ceylon, con su énfasis sobre los tipos estáticos, hace las cosas diferente, Ceylon no tiene un
cambio de tipos al estilo de C. En ves de ellos, debemos de probar y reducir el tipo de la referencia de un objeto en un
solo paso, usando la construcción especial if (is ...). Esta construcción es muy parecida a if (exists ...) y if (nonempty
...), las cuales conocimos tempranamente.
void PrintIfPrintable(Object obj) {
if (is Printable obj){
obj.printObj();
}
}

También hay una construcción especial if (!is ...) que es practica algunas veces.
La declaración switch puede ser usada de una manera similar:
void switchingPrint(Object obj) {
switch(obj)
case (is Hello) {
obj.printMsg();
}
case (is Person) {
print(obj.firstName);
}
else {
print(obj.string);
}
}

47
Un tour por Ceylon, Release

Estas construcciones nos protegen de escribir inadvertidamente codigo que pueda causar una ClassCastException en
Java, justo como if (exists ...) nos protege de escribir codigo que pueda causar NullPointerException.
La construcción if (is ...) actualmente reduce el código a un tipo intersección.

8.2 Tipos intersección

Una expresión es asignable a un tipo intersección, escrito X&Y, si este es asignable a ambos X&Y. Por ejemplo,
desde que Tuple es un subtipo de Iterable<Nothing> y de Correspondence, la tupla tipo [String,String] es también un
subtipo de la intersección.
Iterable<String>&Correspondence<Integer,String>. El supertipo de un tipo intersección incluye todos los supertipos
de cada tipo interceptado.
Entonces, el siguiente código esta bien tipado:
Iterable<String>&Correspondece<Integer,String> string =
["Hello", "world"];
String? str = strings.get(0);
Integer size = strings.size;

Ahora considerar este código, para ver el efecto de if (is ...):


Iterable<String> strings = ["hello","world"];
if (is Correspondence<Integer,String> strings) {
//Aqui string tiene el tipo
//Iterables<String> & Correspondence<Integer,String>
String? str = strings.get(0);
Integer size = strings.size();
}

Dentro del cuerpo de la construcción if, strings tiene el tipo Iterable<String>&Correspondence<Integer,String>, así
podemos llamar las operaciones de ambos, Iterable y Correspondence.

8.3 Tipos unión

Una expresión es asignable a un tipo unión, escrito X|Y, si este es asignable a cualquiera X o Y. El tipo X|Y es siempre
un supertipo de ambos X y Y. El siguiente código esta bien tipado:
void printType(String|Integer|Float val) { ... }

printType("hello");
printType(69);
printType(-1.0);

Pero, ¿qué operaciones tiene un tipo como String|Integer|Float? ¿Cuales son sus supertipos? Bueno, la respuesta es
muy intuitiva: T es un supertipo de X|Y si y solo si es un supertipo de X y de Y. El compilador de Ceylon determina
esto automáticamente. Así el siguiente código esta bien tipado.
Integer|Float x= -1;
Number num = x; // number es un supertipo de ambos Integer y Float
String|Integer|Float val = x; // String|Integer|Float es un supertipo
// de Integer|Float
Object obj = val; //Object es un supertipo de String, Integer y Float.

Sin embargo, el siguiente código no esta bien tipado, desde que Number no es un supertipo of String.

48 Capítulo 8. Unión, intersección y tipos enumerados


Un tour por Ceylon, Release

String|Integer|Float x = -1;
Number num = x; //compile error: String is not a subtype of Number

Por supuesto, es muy común reducir una expresión de tipo unión usando una declaración switch. Usualmente el
compilador de Ceylon nos fuerza a escribir una clausula else en un switch para recordarnos que puede haber casos
adicionales que no han sido manejados. Pero si agotamos todos los casos de un tipo unión, el compilador nos permitirá
dejar fuera la clausula else.
void printType(String|Integer|Float val) {
switch (val)
case (is String) { print("String: ‘‘val‘‘"); }
case (is Integer) { print("Integer: ‘‘val‘‘"); }
case (is Float) { print("Float: ‘‘val‘‘"); }
}

Un tipo unión es una especia de tipo enumerado.

8.4 Tipos Enumerado

Algunas veces es útil que este disponible realizar el mismo tipo de cosas con subtipos de una clase o interfaz. Primero,
necesitamos explícitamente enumerar los subtipos de el tipo usando la clausula of :
abstract class Point()
of Polar | Cartesian {
//...
}

(Esto crea un punto en la versión de Ceylon de lo que llaman en la comunidad de programación funcional un tipo
“algebraico” o “sum”.
Ahora el compilador no nos permitirá declarar subclases adicionales a Point, así el tipo unión Polar|Cartesian es
exactamente el mismo tipo como Point. Ademas podemos escribir declaraciones switch sin la clausula else:
void printPoint(Point point) {
switch (point)
case (is Polar){
print("r = " + point.radius.string);
print("theta = " + point.angle.string);
}
case (is Cartesian) {
print("x = " + point.x.string);
print("y = " + point.y.string);
}
}

Ahora, es usualmente considerado una mala practica escribir largas declaraciones de switch que manejen todos los
subtipos de un tipo. Esto crea un código no extensibles. Agregar una nueva subclase a Point significa romper todas las
declaraciones que agotaron los subtipos. En programación orientada a objetos, tratamos de refactorizar construcciones
como esta usando un método abstracto de la superclase que es sobrescrita apropiadamente por subclases.
Sin embargo, esta es una clase de problema donde este tipo de refactorisación no es apropiado. En muchos lenguajes
de programación orientados a objetos, estos problemas son usualmente resueltos usando el patrón “visitor”.

8.4.1 Visitors

Consideremos las 3 siguientes implementaciones visitor:

8.4. Tipos Enumerado 49


Un tour por Ceylon, Release

abstract class Node() {


shared formal void accept(Visitor v);
}

class Leaf(shared object element)


extends Node() {
accept (Visitor v) => v.visitLeaf(this);
}

class Branch(shared Node left, shared Node rigth)


extends node() {
accept(Visitor v) => v.visitBranch(this);
}

interface Visitor {
shared formal void visitLeaf(Leaf l);
shared formal void visitBranch(Branch b);
}

Podemos crear un método que imprima las tres implementando la interfaz Visitor:
void printTree(Node node) {
object printVisitor satisfies Visitor {
shared actual void visitLeaf(Leaf leaf) {
print("Found a leaf: ‘‘leaf.element‘‘!");
}
shared actual void visitBranch(Branch branch){
branch.left.accept(this);
branch.rigth.accept(this);
}
}
node.accept(printVisitor);
}

Note que el código de printVisitor luce como la delaración de switch. El deberá explícitamente enumerar todos los
subtipos de Node. Estará “roto” si agregamos un nuevo subtipo de Node a la interfaz Visitor. Esto es correcto, y es el
comportamiento deseado; “roto” significa que el compilador nos permitirá conocer que tenemos que actualizar nuestro
código para manejar un nuevo subtipo.
En Ceylon podemos lograr el mismo efecto, con menos verbosidad, enumerando los subtipos de Node en su definición
y usando un switch:
abstract class Node() of Leaf | Branch {}

class Leaf(shared Object element)


extends Node() {}

class Branch(shared Node left, shared Node right)


extends Node() {}

Nuestro método print() es ahora mucho mas simple, pero aun tiene el mismo comportamiento deseado de “romperse”
cuando un nuevo subtipo de Node es agregado.
void printTree(Node node) {
switch (node)
case (is Leaf) {
print("Found a leaf: ‘‘node.element‘‘!");
}
case (is Branch) {

50 Capítulo 8. Unión, intersección y tipos enumerados


Un tour por Ceylon, Release

printTree(node.left);
printTree(node.right);
}
}

8.4.2 Interfaz enumeradas

Ordinariamente, Ceylon no nos permitirá usar tipos interfaz como un case de switch. Si File,Directory, y Link son
interfaces, no podemos escribir tal cual:
File|Directory|Link resources = ...;
switch (resources)
case (is File) { ... }
case (is Directory) { ... } //compile error: cases are not disjoint
case (is Link) { ... } //compile error: cases are not disjoint

El problema es que los casos no están disjuntos. Podemos tener una clase que satisfaga ambos File y Directory y
entonces no saber que rama ejecutar.
(En todos nuestros ejemplos anteriores, nuestros case referenciaban a tipos que eran probablemente disjuntos, debido
a que eran clases, que soportan únicamente herencia simple.)
Hay una solución, a pesar de todo. Cuando una interfaz tiene subtipos enumerados, el compilador fuerza a estos
subtipos a estar disjuntos. Así que si definimos la siguiente interfaz enumerada:
interface Resource of File|Directory|Link { ... }

Entonces la siguiente declaración es un error:


class DirectoryFile()
satisfies File&Directory {} //compile error: File and Directory are
// disjoint types

Ahora esto es aceptado por el compilador:


Resource resource = ...;
switch (resource)
case (is File) { ... }
case (is Directory) { ... }
case (is Link) { ... }

El compilador es muy inteligente cuando razón acerca de las desuniones y agotamiento. Por ejemplo, esto es aceptable:
Resources resource = ...;
switch (resource)
case (is File|Directory) { ... }
case (is Link) { ... }

Como este, asumiendo la anterior declaración de Resource:


File|Link resource = ... ;
switch (resource)
case (is File) { ... }
case (is Link) { ... }

Si estas interesando en conocer mas acerca de esto, lee esto

8.4. Tipos Enumerado 51


Un tour por Ceylon, Release

8.4.3 Instancias enumeradas

Ceylon no tiene algo exactamente como la declaración enum de Java. Pero emular el efecto usando la clausula of.
abstract class Suit(String name)
of hearts|diamons|clubs|spades {}

object hearts extends Suit("hearts") {}


object diamonds extends Suit("diamonds") {}
object clubs extends Suit("clubs") {}
object spades extends Suit("spades") {}

Es permitido usar los nombres de las declaraciones object en la clausula of.


Ahora podemos agotar todos los casos de Suit en un switch:
void printSuit(Suit suit) {
switch (suit)
case (hearts) { print("Heartzes"); }
case (diamonds) { print("Diamondzes"); }
case (clubs) { print("Clidubs"); }
case (spades) { print("Spidades"); }
}

Note que estos casos son casos de valor, no casos de tipo case (is ...). Ellos no necesitan reducir el tipo Suit
Así es esto es un poco mas verboso que el enum de Java, pero también es algo mas flexible.
Para mas ejemplos prácticos, revisa la definición de Boolean y Comparison en el modulo del lenguaje.

8.4.4 Aun hay mas

En la siguiente estación veremos el sistema genérico de tipos en profundidad.

52 Capítulo 8. Unión, intersección y tipos enumerados


CAPÍTULO 9

Generics

Esta es la novena parte de el tour de Ceylon. El la parada anterior cubrimos os tipos unión, intersección y enumerados.
En esta parte vamos a ver acerca de los tipos genéricos.
Herencia y subtipos son poderosas herramientas para la abstracción sobre los tipos. Pero esta herramienta tiene sus li-
mitantes. No nos ayuda a expresar contenedores de tipos genéricos como colecciones. Para este problema necesitamos
tipos parametrizados. Ya hemos visto muchos tipos parametrizados - por ejemplo, iterables, secuencias y tuplas - pero
ahora vamos a explorar esto a detalle.

9.1 Definiendo tipos genéricos

Programar con tipos genéricos es una de las partes mas difíciles en Java. Esto es aun verdad, en cierta parte en Ceylon.
Pero debido a que el lenguaje Ceylon y el SDK fueron diseñados para generics desde cero, Ceylon esta diseñado para
aliviar demasiados aspectos dolorosos de modelo de Java.
Justo como en Java, únicamente tipos y métodos puede declarar parámetros de tipo. También como en Java, los
parámetros de tipo son antes que los parámetros ordinarios, y encerrados entre corchetes angulares.
shared interface Iterator<out Element> { ... }

shared class Singleton<out Element>(Element element)


extends Object()
satisfies [Element+]
given Element satisfies Object { ... }

shared Value sum<Value>({Value+} values)


given Value satisfies Summable<Value> { ... }

shared <key->Item>[] zip<Key,Item>({Key*} keys, {Item*} items)


given Key satisifes Object
given Item satisfies Object { ... }

Como puedes ver, la convención en Ceylon es usar nombres significativos para los parámetros de tipo (en otros len-
guajes de programación la convención es usar letras).
Un parámetro de tipo puede tener un parámetro por defecto.
shared interface Iterable<out Element, out Absent=Null> ...

53
Un tour por Ceylon, Release

9.2 Argumentos de tipo

A diferencia de Java, siempre necesitamos especificar el parámetro tipo en la declaración de tipo (No hay tipos raw
en Ceylon. El siguiente código no compilara:
Iterator it = ...; //error: missing type agmuente to parameter Element of Iterable

En vez de ello, necesitamos proveer el argumento tipo como esto:


Iterator<String> it = ...;

Por otro lado, no necesitamos explícitamente especificar el tipo de los argumentos en muchas invocaciones de métodos
o instanciaciones de clases. No necesitamos usualmente tener que escribir esto:
Array<String> strings = array<String> { "Hello", "World" };
{<Integer->String>*} things = entries<String>(strings);

En vez de ello, es posible inferir los tipos de los argumentos desde argumentos ordinarios.
value string = array { "Hello", "World" }; //tipo: Array<String>
value things = entries(strings); //tipo: Iterable<Entry<Integer,String>>

El algoritmo de inferencia de tipos generic esta ligeramente involucrado, así que deberás referirte a las especificacio-
nes del lenguaje para una definición completa. Pero esencialmente lo que pasa es que Ceylon infiere el tipo de un
argumento combinando los tipos de los correspondientes argumentos usando unión en el caso de un parámetro de tipo
covariante o intersección en el caso de un parámetro de tipo contravariante.
value points = array { Polar(pi/4, 0.5), Cartesian(-1.0, 2.5) }; // tipo: Array<Polar|Cartesian>
value entries = entries(points); //tipo: Entries<Integer,Polar|Cartesian>

Si un parámetro de tipo tiene un argumentos por defecto, esta permitido dejarlo fuera cuando subministremos la lista
de argumentos de tipo. Entonces Iterable<String> significa Iterable<String,Null>.

9.3 Covarianza y contravarianza

Ceylon elimina una de la partes de los generics de Java que hacían realmente difíciles las cosas: tipos wildcard. Los
tipos wildcard fueron la solución al problema de covarianza en un sistema de tipos genérico en Java. Conozcamos
la idea de covarianza, y entonces podremos ver como trabaja la covarianza en Ceylon.
Esto comienza con la intuitiva expectación de que una colección de geeks es una colección de Person. Esta es
una intuición razonable, pero si la colección mutan, esto cambiara a ser incorrecto. Consideremos la siguiente posible
definición de Collection:
interface Collection<Element> {
shared formal Iterator<Element> iterator();
shared formal void add(Element x);

Y vamos a suponer que Geek es un subtipo de Person. La expectación intuitiva es que el siguiente código deberá
de funcionar:
Collection<Geek> geeks = ... ;
Collection<Person> people = geeks; //compile error
for (person in people) { ... }

Este código es, francamente, perfectamente razonable tomado enserio. Aun en ambos Ceylon y Java, este código
resulta en un error en tiempo de compilación en la segunda linea, donde Collection<Geek> es asignado a una

54 Capítulo 9. Generics
Un tour por Ceylon, Release

collection<Person>. ¿Por qué? Bueno, debido a que si permitimos la asignación, el siguiente código deberá
también compilar:
Collection<Geek> geeks = ...;
Collection<Person> people = geeks; //compile error
people.add( Person("Fonzie") );

¡No podemos permitir que el código de Fonzie sea un Geek!


En otras palabras, diremos que Collection es invariante en Element. O, cuando no estemos tratando de im-
presiones gente con terminología confusa, podremos decir que Collection producen ambos a través del métodos
iterator() y consume a través del método add() el tipo Element.
Aquí es donde Java queda fuera y se dirige a abajo por el agujero del conejo, exitosamente usando wildcards para
disputar un tipo covariante o contravariante de un tipo invariante, pero también exitosamente dejando confusos a todos.
No vamos a seguir a Java hasta el fondo del agujero.
En vez, vamos a refactorizar Collection en una interfaz puramente Producer y en una puramente Consumer:
interface Producer<out Output> {
shared formal Iterator<Output> iterator();
}
interface Consumer<in Input> {
shared formal void add(Input x);
}

Note que hemos anotado los parámetros de tipo de estas interfaces.


La anotación out especifica que Producer es covariante en Output Esto es que produce una instancia de
Output, pero nunca consume instancias de Output.
El anotación in especifica que Consumer es una contravariante de Input. Esto es que consume una instancia
Input, pero nunca produce una instancia de Input.
El compilador de Ceylon valida el esquemas de la declaración del tipo y se asegura que la anotaciones de varianza
estén satisfechas. Si tratas de declarar un método add() en Producer dara como resultado un error de compilación.
Si tratas de declarar un método iterate() en Consumers obtendrás en error de compilación similar.
Ahora, veamos que sacamos de esto:
Desde que Producer es covariante en su parámetro de tipo output, y desde que Geek es un subtipo de
Person, Ceylon nos permite asignar Producer<Geek> a Producer<Person>.
Además, desde que Consumer es una contravariante en su parámetro de tipo Input, y desde que geek es un
subtipo de Person, Ceylon nos permite asignar Consumer<Person> a Consumer<Geek>.
Y así poder definir nuestra interfaz Collection como una mezcla de Producer con Consumer.
interface Collection<Element>
satisfies Producer<Element> & Consumer<Element> {}

Note que Collection permanece invariante en Element. Si tratamos de agregar una anotación de varianza a
Element en Collection dará como resultado un error de compilación, debido a que la anotación deberá contra-
decir la anotación de varianza de cualquiera Producer o Consumer.
Ahora el siguiente código finalmente compila:
Collection<Geek> geeks = ...;
Producer<Person> people = geeks;
for (person in people) { ... }

El cual coincide con nuestra intuición original.

9.3. Covarianza y contravarianza 55


Un tour por Ceylon, Release

El siguiente código también compila:


Collection<Person> people = ...;
Consumer<Geek> geekConsumer = people;
geekConsumer.add( Geek("James") );

Que es también intuitivamente correcto - “James” deberá ser una persona.


Hay dos elementos adicionales a la definición de covarianza y contravariaza:
Producer<Anything> es un supertiopo de Producer<T> para cualquier tipo T, y
Consumer<Nothing> es un supertipo de Consumer<T> para cualquier tipo T.
Estas invariantes pueden ser útiles si necesitas abstraer todos los Producers o todos los Consumers. (Nota, sin
embargo, si Producer declaro restricciones obligatorias para tipos en Output, entonces Producer<Anything>
no deberá ser un tipo legal.)
No gastaras mucho tiempo escribiendo tus propias colecciones, desde que Ceylon SDK deberá próximamente tener
un poderoso framework para construir colecciones. Pero aun deberás apreciar el enfoque de Ceylon a la convarianza
como un usuario de los tipos colección incorporados.

9.3.1 Covarianza y contravarianza con unión e intersección

Hay un conjunto de relaciones interesantes que surge cuando introducimos los tipos unión e intersección en la pintura.
Primero, consideremos un tipo covariante como List<Element>. Entonces para cualquier tipo X y Y:
List<X>|List<Y> es un subtipo de List<X|Y>, y
List<X>&List<Y> es un supertipo de List<X&Y>.
Después, consideremos un tipo contravariante como Consumer<Element>. Entonces para cualquier tipo X y Y:
Consumer<X>|Consumer<Y> es un subtipo de Consumer<X&Y>, y
Consumer<X>&Consumer<Y> es un supertipo de Consumer<X|Y>.
Esto es valioso volveremos a esta sección mas adelante, y trataremos de desarrollar alguna intuición acerca del por que
estas relaciones son correctas y que significan. No gastes tu tiempo en esto por ahora. Tenemos cosas mas importantes
que hacer.

9.3.2 Generics y herencia

Considere las siguientes clases:


class LinkedList()
satisfies List<Object> { ... }

class LinkedStringList()
extends LinkedList()
satisfies List<String> { ... }

Este tipo de herencia es ilegal en Java. Una clase no puede heredar el mismo tipo mas de una vez, con diferentes
argumentos de tipo. Podemos decir que Java suporta únicamente single instantiation inheretance.
Ceylon es menos restrictivo en este aspecto. El código anterior es perfectamente legal si (y solo si) la interfaz
List<Element> es covariante en sus parámetros de tipo Element, que es, declarado como esto:
inteface List<out Element> { ... }

56 Capítulo 9. Generics
Un tour por Ceylon, Release

Diremos que Ceylon cuenta con principal instantiation inheritance. Incluso el siguiente código es
legal:
interface ListOfSomething satisfies List<Something> { }
interface ListOfSomthingElse satisfies List<SomethingElse> {}
class MyList() satisfies ListOfSomething & ListOfSomethingElse { ... }

Entonces el siguiente código es lagal y bien tipado:


List<Something&SomethingElse> list = MyList()

Por favor hagamos una pausa aquí, y toma tu tiempo para notar que tan ridículamente impresionante es esto. No-
sotros nunca mencionamos explícitamente que MyList() fue una List<Something&SomethingElse>. El
compilador solo lo dedujo por nosotros.
Note que cuando heredaste el mismo tipo mas de una vez, tu tal vez necesites refinar algunos de sus miembros, en
orden para satisfacer todas las firmas heredadas. No te preocupes el compilador te lo notificara y te obligara a hacerlo.

9.3.3 Restricciones en tipos generic

Es muy común, cuando estamos escribiendo un tipo parametrizado, queremos invocar un método o evaluar un atributo
a instancias del parámetro de tipo. Por ejemplo, si estamos escribiendo un tipo parametrizado Set<Element>,
necesitamos ser capaces de comparar instancias de Element usando == para ver si cierta instancia de Element es
contenida en el Set. Desde que == esta definido para expresiones de tipo Object necesitamos alguna manera de
asegurar que Element es un subtipo de Object. Este es un ejemplo de una restricción de tipo - de hecho, este es un
ejemplo del caso mas común de restricción de tipo, un upper bound.
shared class Set<out Element>(Element* elements)
given Element satisifes Object {

...

shared Boolean contains(Object obj) {


if (is Element obj){
return obj in bucket(obj.hash);
}
else {
return false;
}

Un argumento de tipo a Element deberá ser un subtipo de Object.


Set<String> set1 = Set("C", "Java", "Ceylon"); //ok
Set<String?> set2 = Set("C", "Java", "Ceylon", null); //compile error

En Ceylon, un parámetro de tipo genérico es considerado un tipo propio, así una restricción de tipo luce mas a una
declaración de una clase o interfaz. Esta es otra forma en la que Ceylon es mas regular que otros lenguajes parecidos
a C.
En futuras versiones de Ceylon, después de la 1.0, también introduciremos soporte para varios tipos adicionales de
restricciones de tipos genéricos. Podrás encontrar mas detalles en las especificaciones del lenguaje.

9.3.4 Tipos genéricos totalmente cosificados

La causa principal de muchos problemas cuando trabajamos con tipos genéricos en Java es el borrado de tipos. Los
parámetros y argumentos de tipo son descartados por el compilador y simplemente no están disponibles en tiempo de
ejecución. Así el siguiente, perfectamente sensible, fragmento de código no deberá compilar en Java;

9.3. Covarianza y contravarianza 57


Un tour por Ceylon, Release

if (is List<Person> list) { ... }


if (is Element obj) { ... }

(Donde elemento es un parámetro de tipo genérico.)


El sistema de tipos de Ceylon ha cosificado los argumentos de tipo genérico, Como Java, el compilador de Ceylon lleva
acabo limpieza, descartando parámetros de tipo desde el esquema de el tipo genérico. En la plataforma de JavaScript,
los tipos son descartados cuando se produce el código de JavaScript. Pero a diferencia de Java, los parámetros de
tipo son cosificados(disponibles en tiempo de ejecución). Los tipos son incluso cosificados cuando se ejecutan en una
máquina virtual de Java.
Así los fragmentos de código anteriores compilan y funciones como se esperan en ambas plataformas. Una vez hemos
terminado de implementar el metamodelo, incluso podrás seras capaz usar reflexión para para descubrir el argumento
de tipo de una instancia de un tipo genérico.
Ahora por supuesto, argumentos de tipos genéricos no son revisada para seguridad de tipos a nivel de la máquina virtual
cuando se esta ejecutando, pero esto no es estrictamente necesario desde que el compilador desde que el compilarlo
ya ha revisado la solidez del código.
Nota de implementación En el release M5 no hemos tenido tiempo de implementar algunas optimizaciones impor-
tantes relacionadas a genéricos cosificados. Entonces, tal vez experimentes algunos problemas usando tipos genéricos
en este release. No te preocupes que resolveremos estos problemas al tiempo en que lancemos la versión de Ceylon
1.0. (¡Por favor haz nos saber tus experiencias!)

9.3.5 Aun hay mas

Ahora estamos listos para mirar una característica muy importante de Ceylon: modularidad.

58 Capítulo 9. Generics
CAPÍTULO 10

Paquetes y módulos

Esta es la décima entrega de el tour de Ceylon. Si en la parte anterior sobre los tipos genéricos te sentiste un poco
abrumado, no te preocupes; esta parte cubrirá algo de material que deberá ser mas fácil llevar acabo. Vamos a poner
atención en tema muy distinto. Aprenderemos acerca de paquetes y módulos.

10.1 Paquetes e importaciones

No existe una declaración packages en los archivos fuente de Ceylon. El compilador determina el paque-
te y el módulo en el que un programa permanecerá mediante la ubicación de archivo fuente en que es de-
clarado. Una clase llamada Hello en el paquete org.jboss.hello deberá ser definida en el archivo
org/jboss/hello/Hello.Ceylon.
Cuando un archivo fuente en un paquete refiere a elemento de un programa toplevel en otro paquete, deberá explí-
citamente importar el elemento del programa. Ceylon, a diferencia de Java, no soporta el uso de nombres calificativos
dentro del código fuente. Así es que no podemos escribir org.jboss.hello.Hello en Ceylon.
La sintaxis de la declaración import es un poco diferente a la de Java. Para importar un elemento de un programa,
escribimos:
import com.redhat.polar.core { Polar }

Para importar varios elementos de un programa de un paquete, escribimos:


import com.redhat.polar.core { Polar, pi }

Para importar todos los elementos de un programa toplevel en un paquete, escribimos:


import com.redhat.polar.core { ... }

Para resolver un conflicto de nombres, podemos renombrar una declaración importada:


import com.redhat.polar.core { PolarCoord=Polar }

Pensamos que renombrar es mucho mas limpio que usar nombres calificativos, podemos incluso renombrar miembros
de un tipo:
import com.redhat.polar.core { Polar { r=radius, theta=angle } }

59
Un tour por Ceylon, Release

10.2 Módulos

El soporte integrado para modularidad es uno de los mayores logros del proyecto Ceylon, pero ¿Qué significa modu-
laridad? Hay varias definiciones para esto:
Soporte para una unidad de visibilidad que es mas grande que un paquete, pero mas pequeño que todos los
paquetes.
Un formato descriptor de un módulo que expresa dependencias entre versiones especificas de módulos.
Un integrado formato de módulo y un repositorio de módulos disponible que es entendido por todas las herra-
mientas escritas para el lenguaje, desde el compilador hasta el IDE.
Un runtime que cuenta con un cargador de clases (un cargador por módulo) y la habilidad para manejar
múltiples versiones de el mismo módulo.
Un ecosistema repositorios de módulos remotos donde desarrolladores pueden compartir sus códigos con otros
desarrolladores.

10.2.1 Nivel de visibilidad de un módulo

Un paquete en Ceylon puede ser compartido o no compartido. Un módulo no compartido (Por defecto) es visible
únicamente a el módulo que contiene el paquete. Podemos crear un paquete compartido proveiendo un descriptor del
paquete.
"The typesafe query API."
shared package org.hibernate.query;

Un paquete shared define parte de la API public de el módulo. Así otros módulos pueden acceder a las declara-
ciones compartidas en un paquete shared.
En tiempo de ejecucuón el paquete es representado por un atributo Package top level llamado package.

10.2.2 Descriptor de módulo

Un módulo deberá especificar explícitamente los módulos de los que depende. Esto es logrado a través de un descriptor
de módulo.
"The best-ever ORM solution"
license "http://www.gnu.org/licenses/lgpl.html"
module org.hibernate ’3.0.0.beta’ {
shared import ceylon.language ’1.0.1’;
import java.sql ’4.0’;
}

En tiempo de ejecución un módulo es representado por un atributo Module top level llamado module.

10.2.3 Archivos módulo y repositorio de módulos

Un paquete de archivos compilan a archivos .class, los descriptores de paquetes y módulos en un estilo Java a un
archivo jar con la extensión .car. El compilador de Ceylon usualmente no produce archivos .class individuales
en un directorio. En vez de ello, produce archivos módulo.
Archivos módulo se encuentran en un repositorio de módulos. Un repositorio de módulos es una estructura de direc-
torio bien definida con un bien definido lugar para cada módulo. Un repositorio de módulos puede ser local(en un
sistema de archivos) o remoto (en internet). Dada una lista de repositorios de módulos, el compilador de Ceylon puede

60 Capítulo 10. Paquetes y módulos


Un tour por Ceylon, Release

localizar las dependencias mencionada en el descriptor del módulo cuando se compila. Y cuando termina la compila-
ción del módulo, el coloca al archivo del módulo resultante en un lugar correcto en el repositorio local. La arquitectura
también incluye soporte para directorios fuente, archivos de código, directorios de documentación del módulo.

10.2.4 Desarrollando módulos en la IDE de Ceylon

Para empezar con los módulos en la ide de Ceylon, ve a Help > Cheat Sheets..., abre el objeto Ceylon y
corre el cheat sheet Introduction to Ceylon Modules, que te guiara paso a paso a través del proceso de
crear un módulo, definiendo sus dependencias y exportando su repositorio de módulos.

Ejemplo: Compilando a un repositorio local o remoto

Supongamos que estas escribiendo net.example.foo. Tu directorio de proyecto puede lucir como este:
README
source/
net/
example/
foo/
Foo.ceylon
FooService.ceylon
module.ceylon
documentation/
manual.html

Aquí el código fuente esta en un directorio llamado source (que es utilizado por defecto y nos salva de tener que pasarle
la opción --src a ceylon compile). Desde el directorio del proyecto(el directorio que contiene el directorio
source) tu puedes compilar usando el comando:
$ ceylon compile net.example.foo

Este comando deberá compilar los archivos de código fuente (Foo.ceylon y FooService.ceylon) en archivo módulo y
publicarlos en el directorio de repositorios por defecto de salida, modules (puedes usar la opción --our build
para publicarlos a build). Ahora tu directorio de proyecto lucirá como esto:
README
source/
net/
example/
foo/
Foo.ceylon
FooService.ceylon
module.ceylon
modules/
net/
example/
foo/
1.0/
net.example.foo-1.0.car
net.example.foo-1.0.car.sha1
net.example.foo-1.0.src
net.example.foo-1.0.src.sha1
documentation/
manual.html

10.2. Módulos 61
Un tour por Ceylon, Release

El archivo .src es el archivo de código que puede ser usado por herramientas tales como el IDE, para búsqueda de
código. El archivo sha1 contiene la suma de verificación del archivo sin la extensión sha1 y puede ser usado para
detectar archivos corruptos.
Puedes generar la documentación del API usando ceylon doc‘ como en el siguiente ejemplo:
$ ceylon doc net.example.foo

Esto deberá crear un


modules/net/example/foo/1.0/module-doc
directorio que contiene la documentación.
Ahora vamos a suponer que tu proyecto obtiene una dependencia en com.example.bar versión 3.1.4. Tienes
que declarar el módulo y la versión de la que depende en tu descriptor module.ceylon , no necesitaras decirle a
ceylon compile en que repositorio buscar para encontrar la dependencia.
Una posibilidad es que tu tengas un repositorio local llamado com.example.bar/3.1.4 si este se encuentra en
tu repositorio por defecto (~/.ceylon/repo) entonces no necesitas hacer nada, el mismo comando hará el trabajo:
$ ceylon compile net.example.foo

Alternativamente, si tienes otro repositorio local puede especificarlo usando la opción --rep.
El Ceylon Herd en un repositorio de módulos online que contiene módulos de Ceylon open source. Co-
mo suele pasar, el Herd es uno de los repositorios por defecto que conoce ceylon compile. Asi si
com.example.bar/3.1.4/ esta en Herd entonces el comando a compilar deberá de permanecer agradablemente
corto.
$ ceylon compile net.example.foo

(Esto es correcto, igual que como vimos anteriormente). Por cierto, puedes deshabilitar los repositorios por defecto
con la opción --d si así lo deseas.
Si com.example.bar/3.1.4 se encuentra en otro repositorio, por decir algo en
http://repo.example.com, entonces el comando deberá ser:
$ ceylon compile --rep \
http://repo.example.com \
net.example.foo

Puedes especificar múltiples opciones --rep como sea necesario si tu tienes dependencias en múltiples repositorios.
Cuando estés listo para publicar el módulo en algún lugar, para que cualquier otra persona pueda usarlo. Vamos a decir
que lo quieres publicar en http://ceylon.example.net/repo. Solo tienes que compilarlo otra vez, pero esta
vez especificando la opción -out.
$ ceylon compile \
--rep http://repo.example.com \
--out http://ceylon.example.net/repo \
net.example.foo

Es de valor señalar que tomando ventaja de las acertadas cosas como el directorio de código fuente y el directorio de
salida, como el que hemos hecho aquí, te salvan de teclear mucho.

10.3 Ejecutando un módulo

La ejecución de un módulo de Ceylon es basado en módulos JBoss, una tecnología que también existe el core de
JBoss AS 7. Dada una lista de repositorios de módulos, la ejecución automáticamente localiza un archivo módulo

62 Capítulo 10. Paquetes y módulos


Un tour por Ceylon, Release

y sus dependencias versionada en los repositorios, incluso descargando archivos desde repositorios remotos si es
necesario.
Normalmente , la ejecución de Ceylon es invocada especificando el nombre de un módulo ejecutable en la linea de
comandos.

10.3.1 Ejemplo: Corriendo un repositorio local o remoto

Vamos a continuar el ejemplo que teníamos donde net.example.foo versión 1.0 fue publicado to
http://ceylon.example.net/repo. Ahora, supongamos que quieres correr el módulo(posiblemente desde otra compu-
tadora).
Si la dependencias (com.example.bar/3.1.4) pueden ser encontrada en los repositorios por defecto el comando para
ceylon run es:
$ ceylon run \
--rep http://ceylon.example.net/repo \
net.example.foo/1.0

Puedes pasar opciones también (que son disponibles al programa a través del objeto top level process):
$ ceylon run \
--rep http://ceylon.example.net/repo \
net.example.foo/1.0 \
my options

Si una de las dependencias no esta disponible desde los repositorios, deberás de especificar un repositorio que las
contenga usando otro rep:
$ ceylon run \
--rep http://ceylon.example.net/repo \
--rep http://repo.example.com \
net.example.foo/1.0 \
my options

El caso mas simple, es donde el módulo y sus dependencias están todos en uno (o mas) de los repositorios por defecto
(tales como Herd o ~/.ceylon.repo).
$ ceylon run net.example.foo/1.0

10.3.2 Ecosistema del repositorio de módulos

Una de las ventajas mas agradables de esta arquitectura es que es posible corren un módulo “directamente desde
internet”, solo tecleando, por ejemplo:
$ ceylon run --rep http://jboss.org/ceylon/modules org.jboss.ceylon.demo/1.0

Y todas las dependencias requeridas son automáticamente descargada como se necesiten.


Ceylon Herd es una es una comunidad de repositorios de módulos donde cualquiera puede contribuir reusando mó-
dulos. Por supuesto, el formato de repositorio de módulos es un standard abierto, así cualquier organización puede
mantener su repositorio de módulos publico.

10.3.3 Aun hay mas

En la siguiente estación veremos el soporte de Ceylon para funciones de orden superior.

10.3. Ejecutando un módulo 63


Un tour por Ceylon, Release

64 Capítulo 10. Paquetes y módulos


CAPÍTULO 11

Funciones

Esta el undécima parte del tour de Ceylon. En la parada anterior anterior hemos visto acerca de los paquetes y los
módulos. Esta parada cubre los temas de clases de primero orden y de orden superior.

11.1 Clases de primer orden y de orden superior

Ceylon no es un lenguaje de programación funcional: debido a que tiene métodos que pueden tener efectos secunda-
rios.Pero tiene algo en común con los lenguajes de programación funcional y es que permiten tratar a las funciones
como valores, lo cual a los ojos de algunas personas hace del lenguaje algún tipo de híbrido. En verdad, esto de tratar
funciones como valores en un lenguaje orientado a objetos no tiene nada nuevo, por ejemplo, Smalltalk es uno de los
primeros y aun uno de los lenguajes de programación de objetos mas puro, fue construido en torno a esta idea. De
cualquier manera, Ceylon, como smalltalk y un numero de otros lenguajes orientados a objetos, permite pasar a una
función como un objeto y llevarlo a través del sistema.
En esta parte, hablaremos acerca del soporte de Ceylon para funciones de primer orden y de orden superior.
El soporte a clases de primer orden significa la habilidad de tratar a funciones como valores, asignarla a variables
y pasarlas como argumento.
Una función de orden superior es una función que acepta funciones como argumentos o devuelve una función.
Esta claro que estas dos ideas van de la mano, así que usaremos el termino “funciones de orden superior” desde ahora
en adelante.

11.2 Representado el tipo de una función

Ceylon es un lenguaje fuertemente tipado. Así que si vamos a tratar a las funciones como valores, la primera pregunta
a contestar es: ¿Cuál es el tipo de una función? Necesitamos una manera de representar el tipo de retorno y los tipos
de los parámetros de una función dentro del sistema de tipos.
Recuerda que Ceylon, no tiene tipos de datos “primitivos”. Un principio solido del diseño es que cada tipo deberá ser
representado dentro del sistema de tipos como una declaración de una clase o una interfaz.
En Ceylon, un simple tipo Callable abstrae todas las funciones. Su declaración es la siguiente:
shared interface Callable<out Return, in Arguments>
given Arguments satisfies Anything[] {}

El parámetro de tipo Return representa el tipo de retorno de la función. El parámetro de tipo secuencial Arguments,
deberá ser una secuencia de tipo, representa los tipos de los parámetros de la función. Podemos representar una lista de

65
Un tour por Ceylon, Release

parámetros como un tipo tupla. Por ejemplo, la lista de parámetros (String s, Float x) es representada como
la tupla [String, Float].
Así, que tomando la siguiente función:
function sum(Interger x, Integer y) => x+y;

El tipo de la función sum() es:


Callabla<Integer,[Integer,Integer]>

¿Qué hay acerca de las funciones void? Bueno, El tipo de retorno de una función void es considerado a ser
Anything. Tal como el tipo de la función print() es:
Callable<Anything,[Anything]>

Habrá algunas personas que tienen un background en lenguajes como ML que tal ves estén esperando que void pueda
ser identificado con algún tipo “unit”, por ejemplos, Null o tal ves []. Pero este enfoque deberá significar que un
método que no sea void deberá no estar disponible para refinar un método void y que una función que no sea de
tipo void no estará disponible para ser asignado a un parámetro funcional void. Sin embargo, código perfectamente
razonable deberá ser rechazado por el compilador.
Note que una función void con una implementación concreta devuelve implícitamente el valor null. Esto es com-
pletamente diferente a una función declarada a devolver el tipo Anything, que puede devolver cualquier calor, pero
deberá hacerlo explícitamente, a través de la declaración return. Las siguientes funciones tiene el mismo tipo,
Anything, pero no hacen exactamente la misma cosa:
Anything hello() {
print("Hello")
return "hello";
}

void hello() {
print("hello");
//retorno implicito de null
}

No deberás de confiar en una función que es declarada void, debido a que puede ser un método que es refinado por
una método no void o una referencia a una función no void.
Podemos abreviar tipos Callable con un poco de azúcar sintáctica:
Integer(Integer,Integer) significa Callable<Integer,[Integer,Integer]>
e igualmente,
Anything(String) significa Callable<Anything,[String]>.

11.3 Definiendo funciones de orden superior

Ahora tenemos suficiente conocimiento para poder escribir funciones de orden superior. Por ejemplo, podemos crear
la función repeat() que ejecute repetidamente una función.
void repeat(Integer times,
Anything(Integer) perform) {
for (i in 1..times) {
perform(i);
}
}

66 Capítulo 11. Funciones


Un tour por Ceylon, Release

Ahora, ejecutemos:
void printNum(Integer n) => print(n);
repeat(10, printNum);

Esto deberá de imprimir los números del 1 al 10 en consola.


Existe un problema con esto. En Ceylon, como veremos después, frecuentemente llamamos funciones usando argu-
mentos con nombre, pero el tipo Callable no necesita codificar el nombre de los parámetros de la función. Así
Ceylon tiene una alternativa, mas elegante, sintaxis para declarar un parámetro de tipo Callable:
void repeat(Integer timer,
void perform(Integer n) ) {
for (i in 1..times) {
perform { n=i; };
}
}

Esta versión es un poco mas legible, así que el la sintaxis preferida.

11.4 Referencias a funciones

Cuando el nombre de una función aparece sin argumentos, como printNum en el anterior ejercicio, es llamada una
referencia a una función. Una referencia a una función es el objeto que realmente es de tipo Callable. En este caso,
printNum tiene el tipo Callable<Anything,Integer>.
Ahora, ¿recuerdas como dijimos que Anything es el tipo de retorno de una función void y también la raíz de la
jerarquía de tipos? Bueno, esto es útil aquí, esto significa que podemos asignar una función de cualquier tipo a un
parámetro que espera una función de tipo void, siempre y cuando coincidan las lista de parámetros:
Boolean attemptPrint(Integer n) {
try {
print(n);
return true;
}
catch (Exception n) {
return false;
}
}

Y podemos mandarla a llamar así:


repeat(10, attemptPrint);

Otra forma en la que podemos producir una referencia a una función es por medio de aplicar parcialmente un método
a una expresión (que recibe).
class Hello(String name) {
shared void say(Integer n) {
print("Hello, ‘‘name‘‘, for the ‘‘n‘‘th time!");
}
}

Y lo ejecutaremos de la siguiente manera:


repeat(10, Hello("Gavin").say);

En la expresión anterior Hello("Gavin").say tiene el mismo tipo que print, es decir es de tipo
Anything(Integer).

11.4. Referencias a funciones 67


Un tour por Ceylon, Release

11.5 Curring

Un método o función puede ser declarado en una forma llamada curried, permitiendo al método o función ser
parcialmente aplicado a sus argumentos. Una función curried tiene múltiples listas de parámetros:
Float adder(Float x)(Float y) => x+y;

La función adder() tiene el tipo Float(Float)(Float). Podemos invocarla con un solo argumento para obte-
ner la referencia a una función de tipo Float(Float), y mantener esta referencia como una función, como esta:
Float addOne(Float y);
addOne = adder(1.0);

O como un valor, como en el siguiente caso:


Float(Float) addOne = adder(1.0);

(La única diferencia entre estos dos enfoques es que en el primer caso le asignamos un nombre a el parámetro de
addOne().)
Entonces subsecuente mente invocamos a addOne(), el actual cuerpo de adder() es finalmente ejecutado, produ-
ciendo un Float.

11.6 Funciones anonimas

Las funciones mas famosas del estilo de orden superior son un trio de funciones que transforman, filtran, coleccionan
secuencias de valores. En Ceylon, estas tres funciones, map(), filter(), y fold() son métodos de la interfaz
Iterable, (Incluso hay una cuarta, una amiga un poco menos glamurosa llamada find(), también un método de
Iterable.)
Como probablemente habrás notado todas las definiciones de funciones han sido declaradas con un nombre, usando
la sintaxis tradicional estilo C. No hay nada errado con pasar un nombre de una función a map() o filter() y de
hecho es útil:
Float max = measurements.fold(0.0, largest<Float>);

Sin embargo, común mente, es un inconveniente tener que declarar una función dándole nombre completa solo para
pasárselo a map(), filter(), fold() o find(). En vez de ello, podemos declarar una función anónima, como
parte de la lista de argumentos.
Float max = measurements.fold(0.0,
(Float max, Float num) =>
num > max then num else max);

Una función anónima consta de:


opcionalmente, la palabra function o void
una lista de parámetros, encerrados entre paréntesis, seguidos por
una flecha gorda, =>, con una expresión o
un bloque.
Así que podemos reescribir lo anterior usando un bloque.
Float max = measurements.fold(0.0,
(Float max, Float num) {

68 Capítulo 11. Funciones


Un tour por Ceylon, Release

return num>max then num else max;


});

Note que es un poco mas difícil dar una buena vista a las funciones anónimas con bloque, así que usualmente es mejor
darle el nombre a una función y usarla como referencia.

11.7 Mas acerca de funciones de orden superior

Veamos un ejemplo practico, que mezcle ambas maneras de representar el tipo de una función.
Supongamos que tenemos algún tipo de interfaz de usuario que puede ser observado por objetos en el sistema. Podemos
usar algo como el patrón de Java Observer/Observable:
interface Observer {
shared formal void observe(Event event);
}

abstract class Component() {


variable {Observer*} observers = {};

shared void addObserver(Observer observer) {


observers = {observer, *observers};
}

shared void fire(Event event) {


for (o in observers) {
o.observe(event);
}
}
}

Pero ahora todos los objetos tienen que implementar la interfaz Observer, que solo tiene un método. ¿Porqué no
simplemente dejamos fuera a la interfaz y permitimos a los observadores de eventos solo registrar un objeto función
como su evento de escucha? En el siguiente código, definimos que el método addObserver acepte una función
como parámetro.
abstract class Component() {
variable {Anything((Event)*} observers = {};

shared void addObserver(void observe(Event event)) {


observers = {observe, *observers};
}

shared void fire(Event event) {


for (observe in observers) {
observe(event);
}
}
}

Aquí podemos ver la diferencia entre las dos maneras de especificar el el tipo de una función:
void observe(Event event) es mas legible en lista de parámetro, donde observe es el nombre del
parámetro, pero
Anything(Event) es útil en el tipo del contenedor tal como un iterable.
Ahora, cualquier observador de evento puede solo pasar una referencia a uno de sus métodos a addObserver():

11.7. Mas acerca de funciones de orden superior 69


Un tour por Ceylon, Release

class Listener(Component component) {

void onEvent(Event e){


//responde al evento
// ...
}

component.addObserver(onEvent);

// ...
}

Cuando el nombre de el método aparece en una expresión sin una lista de argumentos después de el, es una referencia
a un método, no una invocación al método. Aquí la expresión de tipo Anything(Event) que refiere al método
onEvent().
Si onEvent() fuese shared, podemos incluso hilar Component y Listener desde algún otro código, para
eliminar la dependencia de Listener sobre Component.
class Listener() {

shared void onEvent() {


// respuesta a el evento
// ...
}
}

void listen(Component component, Listener listener) {


component.addObserver(listener.onEvent);
}

Aquí la sintaxis de listener.onEvent() es un tipo de aplicación parcial del método onEvent(). Esto no
causa que el método sea ejecutado(debido a que no hemos provisto una lista de parámetros aún). En vez, resulta en
una función que empaqueta juntos a el método referencia onEvent y el método recibidor listener.
Es también posible declarar un método que devuelva una función. Vamos a considerarla habilidad para remover ob-
servadores desde un Component. Podemos usar una interfaz subscription:
interface Subscription {
shared formal void cancel();
}

abstract class Component() {


variable {Anything(Event)*} observers = {};

shared Subscription addObserver(void observe(Event event)) {


observers = {observe,*observers};
object subscription satisfies Subscription {
cancel() => observers =
{ for (o in observers) if (o!=observe) o };
}
return subscription;
}

shared void fire(Event event) {


for (observe in observers) {
observe(event);
}
}

70 Capítulo 11. Funciones


Un tour por Ceylon, Release

Pero una solución simple puede ser solo eliminar la interfaz y devolver el método cancel() directamente:
abstract class Component() {
variable {Anything(Event)*} observers = {};

shared Anything() addObserver(void observe(Event event)) {


observers = {observe,*observers};
return void () => observers =
{ for (o in observers) if (o!=observe) o };
}

shared void fire(Event event) {


for (observe in observers) {
observe(event);
}
}
}

Aquí, hemos definido una función anónima dentro de el método addObserver(), y retornar una referencia a esta
función fuera del método. La referencia a la función anónima devuelta por addObserver() puede ser llamada por
cualquier código que obtenga la referencia..
En caso que te estés preguntando el tipo de la función que se encuentra dentro del método addObserver() es
Anything()(Anything(Event).
Note que la función anónima esta habilitada para usar el parámetro observe de addObserver(). Diremos que el
método anidado recibe una closure de las no variable locales y parámetros desde afuera del método - Justo como
un método de una clase recibe una closure de la clase inicializando parámetros y locales de la clase inicializador.
En general, cualquier declaración de clase anidad, método o atributo siempre recibe la closure de la declaración
de los miembros de la clase, método o atributo en que este es encerrado. Este es un ejemplo de que tan regular es el
lenguaje.
Podemos invocar nuestro método de la siguiente manera:
addObserver(onEvent)();

Pero si estamos planeando usar el método de esta manera, probablemente no se buena razón para darle dos listas de
parámetros. Esto es mucho mas probable que cuando estábamos planeando mantener o pasar la referencia a el método
anidado en algún lugar método antes de invocarlo.
Anthing() cancel = addObserver(onEvent);

//...
cancel();

La primera linea demuestra como la referencia de una función puede ser mantenida. La segunda linea de código
simplemente invoca la referencia devuelta a cancel().

11.8 Composición y curry

La función compose() lleva acaba composición de funciones. Por ejemplo, dadas las funciones print() y
plus() en ceylon.language, con la siguiente forma:

11.8. Composición y curry 71


Un tour por Ceylon, Release

shared void print(Anything line) { ... }

shared Value plus<Value>(Value x, Value y) { ... }

Podemos ver que el tipo de referencia de la función print() es Anything(Anything), y el tipo de la referencia
a la función plus<Float> es Float(Float, Float). Entonces podemos escribir lo siguiente:
Anything(Float, Float) printSum = compose(print,plus<Float>);
printSum(2.0,2.0); //imprime 4.0

La función curry() produce una función con múltiples listas de parámetros, dada una función con múltiples lista de
parámetros:
Anything(Float)(Float) printSumCurried = curry(printSum);
Anything(Float) printPlus2 = printSumCurried(2.0);
printPlus(2.0); //imprime 4.0

La función uncurry() hace lo opuesto, dándonos la forma original.


Anything(Float,Float) printSumUncurried = uncurry(printSumCurried);

Note que compose(), curry y uncurry() son funciones ordinarias escritas en Ceylon.

11.9 El operador spread

Ya hemos visto unos pocos ejemplos de el operador spread. Hemos visto como usarlo para instanciar un iterable:
{ "hello", *names }

O una tupla:
[x, y, *labels]

También podemos usarlo cuando llamamos una función. Considere la siguiente función.
String formatDate(String format,
Integer day,
Integer|String month,
Integer year) {
...
}

Y supóngase que tenemos una tupla representando una fecha:


value date = [15, "January", 2010];

Entonces podemos pasar la fecha a nuestra función como lo siguiente:


formatDate("dd MMMMMM yyyy", *date);

Note que el tipo de la tupla ["dd MMMMMM yyyy", *date] es:


[String,Integer,String,Integer]

Ahora considerar el tipo de la función formatDate:


String(String,Integer,Integer|String,Integer)

O también:

72 Capítulo 11. Funciones


Un tour por Ceylon, Release

Callable<String,[String,Integer,Integer|String,Integer]>

Desde que la tupla tipo [String,Integer,String,Integer] es un subtipo de


[String,Integer,Integer|String,Integer], la invocación esta bien tipada. ¡Esto demuestra la
relación entre tuplas y argumentos de función!.

11.10 Aún hay más

Podrás encontrar una discusión mas detallada de como Ceylon represente tipos de funciones usando tuplas aqui inclu-
yendo una discusión a detalle de compose() y curry().
Ahora es turno de la sintaxis para lista de argumentos con nombre y para definir interfaces de usuario e información
estructurada.

11.10. Aún hay más 73


Un tour por Ceylon, Release

74 Capítulo 11. Funciones


CAPÍTULO 12

Argumentos con nombre

Esta es la duodécima parada del tour de Ceylon. En la parada anterior aprendimos acerca de funciones. Esta parte se
enfoca en cubrir el soporte de Ceylon para usar argumentos con nombre.

12.1 Argumentos con nombre

Considere la siguiente función:


void printf(Writer to, String format, {Object*} values) {
//...
}

Hemos visto cientos de ejemplos de invocar una función o instanciar una clase usando una sintaxis similar a C, donde
los argumentos son delimitados por paréntesis y separados por coma. Argumentos son asignados a parámetros por su
posición en la lista. Veamos un ejemplo más:
printf(writer,
"Thanks, %s. You have been charged %.2f.
Your confirmation numbers is %d.",
{ user.name, order.total, order.confirmationNumber });

Esto trabaja bien. Sin embargo, Ceylon ofrece un alternativo protocolo de invocación de una función que es usual
mente fácil de leer cuando hay más de uno o dos argumentos:
printf {
to = writer;
format = "Thanks, %s. You have been charged %.2f.
Your confirmation number is %d.",
{ user.name, order.total, order.confirmationNumber }
};

Esta invocación es llamada lista de argumentos con nombre. Podemos reconocer una lista de argumentos con nom-
bre por que utiliza llaves en ves de paréntesis. Note que los argumentos son separados por punto y coma. Hemos
especificado el nombre de cada parámetro.
Usual mente le damos un formato en múltiples lineas a la invocación de argumentos con nombre.

75
Un tour por Ceylon, Release

12.2 Argumentos iterables

Desde que el parámetro values es de tipo Iterable, se nos esta permitido abreviarlo, dejando fuera el nombre del
parámetro y las llaves que encierran a la expresión iterable.
printf {
to = writer;
format = "Thanks, %s. You have been charged %.2f.
Your confirmation number is %d.";
user.name, order.total, order.confirmationNumber
}

De hecho, podemos dejar fuera los nombres de los parámetros completamente.

12.3 Dejando fuera los nombres de los parámetros

Contraria a la descripción de la característica “lista de argumentos con nombre”, podemos actualmente dejar fuera los
nombres de el parámetro si escribimos los argumentos descendente mente en el orden correcto:
printf {
writer;
"Thanks, %s. You have been charged %.2f.
Your confirmation number is %d.";
user.name, order.total, order.confirmationNumber
}

Si, existe una muy buena razón para esto, ¡estamos a punto de verla!

12.4 Sintaxis declarativa para instanciar objetos

Las siguientes clases definen una estructura de datos para definir tablas:
class Table(String title, Integer rows, Border border,
{Column*} columns) {}

class Column(String heading, Integer width,


String content(Integer row) {}

class Border(Integer padding, Integer weight) {}

Por supuesto, podemos construir una Table usando una lista de argumentos posicionales y funciones anónimas:
Table table = Table("Squares", 5, Border(2,1),
{ Column("x",10, (Integer row) => row.string),
Column("x^2",12,(Integer row) => (row^2).string) });

Sin embargo, es mucho mas común usar una lista de parámetros con nombre para construir un complejo gráfico de
objetos. En esta sección vamos a conocer algunas nuevas características de la lista de argumentos con nombre, que
hacen especialmente conveniente construir gráficos de objetos.
Primero, note que la sintaxis que ya hemos usado para especificar el valor de un argumento con nombre se ve exac-
tamente como la sintaxis para refinar un atributo formal. Si tu lo piensas así, teniendo en cuenta que un parámetro
de una función puede aceptar referencia a otras funciones, todo el problema de especificar valores para argumentos
con nombre comienza a parecerse mucho como el problema de refinar miembros abstractos. Entonces, Ceylon deberá
permitirnos reusar mucha sintaxis de la declaración de los miembros dentro de una lista de argumentos con nombre.

76 Capítulo 12. Argumentos con nombre


Un tour por Ceylon, Release

Es completamente legal incluir las siguientes construcciones en una lista de argumentos con nombre:
Declaración de funciones - especifica el argumentos de un parámetro que acepte una función.
declaración de object(clase anónima) - son útiles para especificar el valor de un argumento con nombre donde
el tipo es una interfaz o una clase abstracta, y
Declaración de getter - nos permite computar el valor de un argumento.
Esto ayuda a explicar por que listas de argumentos con nombre son delimitadas por llaves: La sintaxis general para
una lista de argumentos es muy muy cercana a la sintaxis para el cuerpo de una clase, función o atributo. Note, otra
vez, cuanta flexibilidad deriva de un lenguaje regular.
Así podemos escribir un código que construye una Table como sigue:
Table table = Table {
title="Squares";
rows=5;
border = Border {
padding=2;
weight=1;
};
Column{
heading="x";
width=10;
function content(Integer row)
=> row.string;
}
Column {
heading="x^2";
width=12;
function content(Integer row)
=> (row^2).string;
}
}

Note que especificamos el valor de el parámetro con nombre content, usando la sintaxis usual para declarar una
función.
Aun mejor, usando el atajo que hemos visto antes, nuestro ejemplo puede ser un poco mas abreviado como este:
Table table = Table {
title="Squares";
rows=5;
Border {
padding=2;
weight=1;
};
Column{
heading="x";
width=10;
content(Integer row)
=> row.string;
}
Column {
heading="x^2";
width=12;
content(Integer row)
=> (row^2).string;
}
}

12.4. Sintaxis declarativa para instanciar objetos 77


Un tour por Ceylon, Release

Note como hemos transformado el código desde una forma que enfatiza invocación a una forma que enfatiza declara-
ción de una estructura jerárquica. Semántica mente, las dos formas son equivalentes. Pero en términos de legibilidad,
son un poco diferentes.
Podemos poner el código anterior totalmente declarativo en su propio archivo y deberá verse como un tipo de “mini-
lenguaje” para definir tablas. De hecho, su óodigo ejecutable puede ser corregido sintáctica mente por el compilador de
Ceylon y después compilado a bytecode de Java o de Javascript. Aun mejor, el IDE de Ceylon deberá proveer soporte
para nuestro mini-lenguaje. En un completo contraste a el soporte DSL en algunos lenguajes dinámicos, ¡cualquier
DSL de Ceylon es completamente seguro en cuanto a tipos! Puedes pensar en la definición de las clases Table,
Column y Border como definiendo el “esquema” o la “gramática” de un mini-lenguaje. (De hecho, ellos realmente
definen una sintaxis de árbol para el mini-lenguaje.)
Ahora veamos un ejemplo de una lista de argumentos con nombre con una declaración getter:
shared class Payment(PaymentMethod method,
Currency currency,
Float amount) {}

Payment payment {
method = user.paymentMethod;
currency = order.currency;
value amount {
variable Float total = 0.0;
for (item in order.items) {
total += item.quantity * item.product.unitPrice;
}
return total;
}
}

Finalmente, aquí hay un ejemplo de una lista de argumentos con nombre con una declaración de object:
shared interface Observable {
shared void addObserver(Observer<Nothing> observer) {
//...
}
}

shared interface Observer<in Event> {


shared formal void on(Event event);
}

observable.addObserver {
object observer
satisfies Observer<UpdateEvent> {
on(UpdateEvent e) =>
print("Update: " + e.string);
}
};

(Note que Observer<T> es asignable a Observer<Nothing> para cualquier tipo T, desde que Observer<T>
es contra variante en su parámetro de tipo T. Si esto no te es entendible, por favor lee la sección de generics otra vez.)
Por supuesto, como hemos visto en la parada anterior de funciones, una mejor manera de resolver este problema puede
ser eliminar la interfaz Observer y pasarle la función directamente:
shared interface Observable {
shared void addObserver<Event>(void on(Event event)) { ... }
}

78 Capítulo 12. Argumentos con nombre


Un tour por Ceylon, Release

observable.addObserver {
void on(UpdateEvent e) =>
print("Update: " + e.string);
};

12.5 Definiendo intefaces de usuario

Uno de los primeros módulos que vamos a crear para Ceylon deberá ser una librería para templates HTML en Ceylon.
Un fragmento de HTML estático deberá lucir como esto:
Html {
Head {
title = "Hello world";
cssStyleSheet = "hello.css";
};
Body {
Div {
cssClass = "greeting";
"hello World"
},
Div {
cssClass = "footer";
"Powered by Ceylon";
}
};
}

Incluso aunque parezca como un tipo de lenguaje de template, es solo una expresión ordinaria.

Importante: Nota de implementación - Milestone 5


Esta libreria aun no existe. Por que no te unes al desarrollo de la plataforma de Ceylon.

12.6 Aún hay más

Hay demasiadas aplicaciones de esta sintaxis del lado de la definición de interfaces de usuario. Por ejemplo, Ceylon
nos permite usar una lista de argumentos con nombre para especificar los argumentos del elemento anotación de un
programa. Pero tendremos que volver a este temas de las anotaciones en una entrega futura.
La siguiente sección aun introduce otra manera de especificar un argumento a una función: comprensión.

12.5. Definiendo intefaces de usuario 79


Un tour por Ceylon, Release

80 Capítulo 12. Argumentos con nombre


CAPÍTULO 13

Comprehensions

Esta es la trigésima parada en el Tour de Ceylon. En el sección anterior hemos visto como invocar funciones usando
argumentos con nombre. Ahora estamos listos para aprender acerca de comprehensions.

13.1 Comprehensions

Un comprenhension es una forma conveniente para transformar, filtrar o combinar un flujo o flujos de valores antes
de pasarle el resultado a la función. Comprehensions actúan sobre ello y producen instancias de Iterable. Un
comprehension puede aparecer en:
dentro de corchetes, produciendo una secuencia.
dentro de llaves, produciendo iterables, o
dentro de una lista de argumentos con nombre.
La sintaxis para instanciar una secuencia, que conocimos anteriormente esta considerada para tener un parámetro de
tipo iterable, así podemos usar comprehension para construir una secuencia:
String[] name = [ for (p in people) p.name ];

¡Pero conprehension no son solo útiles para construir secuencias! Supongamos que tenemos una clase HashMap,
con la siguiente forma.
class HashMap<Key,Item>({Key->Item*} entries) { ... }

Entonces podemos construir un HashMap<String,Person> como en lo siguiente:


value peopleByName = HashMap { for (p in people) p.name->p };

Como ya has podido suponer,la clausula for de comprehension funciona un poco como el ciclo for que vimos
anteriormente. El toma cada elemento de el flujo Iterable en turno. Pero esto lo hace lazily, cuando la función
de recepción actualmente itera su argumento.
Esto significa que si la función de recepción no necesita iterar el flujo completo, el comprehension nunca sera
totalmente evaluado. Esto es extremamente útil para funciones como every() y any():
if (every { for (p in people) p.age>=18 }) { ... }

La función every() (en ceylon.language)acepta un flujo de valores booleanos, y detiene su iteración tan pronto
como encuentre false en el flujo.
Si necesitamos almacenar el flujo de valores en algún lugar, sin evaluar ninguno de sus elementos, podemos usar una
expresión para construir iterables, como este:

81
Un tour por Ceylon, Release

{String*} names = { for (p in people) p.name };

Ahora veamos lo que los varios tipos de comprehension hacen.

13.2 Transformación

La primer cosa que podemos hacer con un comprehension es transformar los elementos del flujo usando una
expresión para producir un nuevo valor para cada elemento. Esta expresión aparece al final de un comprehension.
Esta es la cosa que el Iterable resultante en realidad itera.
Por ejemplo, esta comprehension
for (p in people) p.name->p

resulta en un Iterable<String->Person>. Para cada elemento de people una nueva


Entry<String,Person> es construida por el operador ->.

13.3 Filtrado

La clausula if de comprehension nos permite evitar ciertos valores de el flujo. Esta Comprehension produce
un flujo de números que son divisibles por 3.
for (i in 0..100) if (i%3==0) i

Esto es especialmente útil para filtrar usando if (exists ...).


for (p in people) if (exists s=p.spouse) p->s

Puedes usar incluso múltiples condiciones de if:


for (p in people)
if (exists s=p.spouse,
nonempty inlaws=s.parents)
p->inlaws

13.4 Productos y uniones

Un comprehension puede tener mas que una clausula for. Esto nos permite combinar dos flujos para obtener un
flujo de valores de su producto cartesiano:
for (i in 0.100) for (j in 0..100) Node(i,j)

Incluso mas útil, nos permite obtener un flujo de valores asociados, como en un join de SQL.
for (o in orgs) for (e in o.employees) e.name

13.5 Aún hay más

Después vamos a discutir algunos de los tipos básicos que hay en el modulo del lenguaje, en particular tipos numéricos
e introducir la idea del polimorfismo de operadores.

82 Capítulo 13. Comprehensions


Un tour por Ceylon, Release

Puedes leer mas acerca de trabar con objetos iterables en Ceylon en este blog.
Para alguna critica o sugerencia puedes contactarme a @ilcapitanozoek en twitter.

13.5. Aún hay más 83

You might also like