============================== 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. --------- 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` representa un objeto iterables que tal vez no genere algún valor cuando es iterado, y puede ser abreviado {X*}, y - El tipo `Iterable` 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: .. code-block:: ceylon {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`. .. code-block:: ceylon if (exists char = text[i], char in {',', '.', '!', '?', ';', ':'}) { //... } .. code-block:: ceylon "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`. ################ Iterando con for ################ Para iterar una instancia de `Iterable`, podemos usar un ciclo `for`: .. code-block:: ceylon 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`: .. code-block:: ceylon for (i -> palabra in entries(masPalabras)) { print("``i``: ``palabra``"); } La función `entry` devuelve una instancia de `Entry`[] conteniendo los elementos indexados de la secuencias. (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: .. code-block:: ceylon for (nombre -> lugar in zip(nombre,lugar)) { print(nombre + " @ " + "lugar"); } ---------- 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` representa una secuencia que puede puede ser vacía y puede ser abreviada `[X*]` o `X[]`, - El tipo `Sequence` 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. .. code-block:: ceylon 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)`. ############################### Azúcar sintáctica en secuencias ############################### Hay mucho mas azúcar sintáctica para secuencias. Podemos utilizar un grupo de sintaxis de Java: .. code-block:: ceylon 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: .. code-block:: ceylon Sequential operators = ...; Null|String plus = operators.get(0); Sequential 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`: .. code-block:: ceylon for (op in operators) { print(op); } ------ Rangos ------ Un `range` es un tipo de `Sequence`. El siguiente codigo: .. code-block:: ceylon Character[] uppercaseLetters = 'A'..'Z'; Intergers[] countDown = 10..0; Es solo azúcar para: .. code-block:: ceylon Sequential uppercaseLetters = Range('A','Z'); Sequential 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. .. code-block:: ceylon variable Integer fac=1; for (n in 1..100) { fac*=n; print("Factorial ``n``! = ``fac``"); } ########################### 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`. - `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`. ##################################### 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`. 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` e `Iterable` son ambos covariantes en el parámetro de tipo `Element`. Así `Empty` es asignable a `Correspondence` y `Iterable` 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`, el sabe que la operación `first` es de tipo `Null`, por ejemplo, que su valor es `null`. Cool, ¿no? #################################### 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` es una interfaz inmutable, este no es un tipo concreto mutable como un array. No podemos establecer un valor de un elemento: .. code-block:: ceylon 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: .. code-block:: ceylon 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. .. code-block:: ceylon for (i -> op in entries(operators)) { //... } Así mismo, frecuentemente no hacemos una prueba adelantada de un indice contra la longitud de la secuencia. .. code-block:: ceylon 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: .. code-block:: ceylon if (exists op = operators[i]) { return op; } else { throw IndexOutBoundException(); } De hecho este es un uso común para `assert`: .. code-block:: ceylon assert(exists op = operators[i]); return op; Especialmente nunca necesitaremos escribir lo siguiente: .. code-block:: ceylon if (i>operators.size-1) { return ""; } else { return operators[i]; //compile error } Es mucho mas limpio y elegante: .. code-block:: ceylon 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`. ------ Tuplas ------ Tuplas es una lista ligada que captura el tipo estático para cada elemento individual en la lista. Por ejemplo: .. code-block:: ceylon [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: .. code-block:: ceylon Tuple>> 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. .. code-block:: ceylon 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: .. code-block:: ceylon 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 degenerada. ########### 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`.