Comprehensiones en Ceylon

Las comprehensiones (o algo que se les parezca) son una característica ya prácticamente obligatoria en los nuevos lenguajes, al menos si quieren ser considerados cool. Y Ceylon no se queda atrás.

Las comprehensiones en Ceylon funcionan usando una variante especial de for, la cual acepta una sola expresión, en vez de un bloque de código. La expresión puede ser a su vez otro for, o bien un if, que actúa como un filtro, o cualquier otra expresión que devuelva algún valor. Todo eso termina siendo azúcar sintáctica para crear y devolver un objeto Iterable que se puede pasar en invocaciones a métodos o funciones que esperan argumentos secuenciados (de los que terminan con elípsis), o usarse directamente en cualquier lugar donde se pueda usar un Iterable.

Esto a fin de cuentas puede ser más poderoso que tener métodos como map y filter en las colecciones, y además permite hacer implementaciones más eficientes, ya que los iteradores intermedios involucrados se van utilizando conforme se necesitan, en vez de tener que procesar completamente una colección antes de pasar a la siguiente fase.

Si usan Groovy o Scala, puede que ya estén familiarizados con algunas de estas operaciones. Por ejemplo, tomar una lista de palabras y devolverlas en reversa:

["hola", "mundo", "javaMexico", "Ceylon"].collect { it.reversed() } //Groovy

List("hola", "mundo", "javaMexico", "Ceylon").map { _.reverse } //Scala

for (w in { "hola", "mundo", "javaMexico", "Ceylon" }) w.reversed; //Ceylon

El concepto es simple, la escritura es simple, la lectura es simple, en los tres casos. Ahora hagamos dos ciclos: De una lista de palabras, tomar cada caracter de cada palabra, y convertirlo a mayúscula, pero la cosa es que al final hay que obtener una sola colección:

["hola","mundo"].collect { it.collect { it.toUpperCase() } }.flatten() //Groovy

//Scala (5 maneras de escribirlo, aunque hay más)
List("hola", "mundo").map { _.map { _.toUpper } } flatten
List("hola", "mundo").map { _.map(_.toUpper) } flatten
List("hola", "mundo").map(_.map { _.toUpper }) flatten
List("hola", "mundo").map(w => w.map {
    c=> c.toUpper
}).flatten
for(w <- List("hola","mundo"); c <- w) print(c.toUpper) //Es como foreach, hay que hacer algo con el elemento

for (w in { "hola", "mundo" }) for (c in w) c.uppercased; //Ceylon

Agreguemos ahora una restricción adicional: Hay que obtener solamente las vocales.

//Groovy (con it, y con parámetros nombrados)
['hola', 'mundo'].collect { it.findAll { 'aeiou'.contains(it) }.collect { it.toUpperCase() } }.flatten()
['hola', 'mundo'].collect { w -> w.findAll { c -> 'aeiou'.contains(c) }.collect { c -> c.toUpperCase() } }.flatten()
//Scala (dejémoslo en 3 maneras: map-filter con/sin placeholders, y el for)
List("hola", "mundo").map { _.filter { "aeiou".contains(_) } map { _.toUpper } } flatten
List("hola", "mundo").map { w => w.filter { c => "aeiou".contains(c) } map { _.toUpper } } flatten
for(w <- List("hola","mundo"); c <- w; if "aeiou".contains(c)) print(c.toUpper)
for (w in {"hello", "world"}) for (c in w) if (c in "aeiou") c.uppercased //Ceylon

Este último ejemplo, que no consiste más que en iterar sobre la lista de palabras, iterar sobre cada caracter de cada palabra, filtrar las vocales y transformar cada una a mayúsculas, resultó ya en código algo complejo tanto en Groovy como en Scala. Y no solamente fue complejo de leer, sino de escribir también; tuve que pensar bastante en lo que estaba escribiendo y obviamente probar que diera el resultado correcto. En el caso de Scala incluso tuve que hacer cambios porque lo que pensaba que iniciamente me iba a dar el resultado, no funcionaba como yo creía (el for es un poco más sencillo, pero cualquier programador con experiencia en Scala te recomienda que no uses el for porque genera código muy ineficiente).

El problema en parte viene por los closures; si usamos los argumentos anónimos en Scala o el it de Groovy, el código se vuelve confuso cuando tenemos closures anidados o incluso que sean separados pero en la misma línea; si nombramos los argumentos para evitar esta confusión, queda un código más extenso, y la manera en que están hechas las declaraciones no mejora mucho la legibilidad.

La ventaja que tiene el uso directo de closures es cuando se quieren hacer cosas complejas; se pueden poner varias líneas de código, mientras que Ceylon tiene la limitante de que se puede usar una sola expresión. Pero dado que Ceylon permite funciones anidadas y con inferencia local de tipos, se puede declarar la función que se quiere usar justo antes de la comprehensión, y usarla ahí mismo, de modo que no es realmente una limitante:

function transformacionCompleja(Integer x) {
  //Aquí puede haber varias líneas de código
  //Y al final hay que devolver algo
  return result;
}
value arreglo = array(for (a in 1..100) if (primo(a)) transformacionCompleja(b));

Hasta se puede leer de forma lineal: crear un arreglo con los números del 1 al 100 que sean primos, aplicándoles la transformacionCompleja a cada uno.

Y además el Iterable generado para la comprehensión se evalúa de manera tardía, de modo que, siguiendo este último ejemplo, la manera en que funciona es más o menos así, en pseudocódigo:

class Iterator {
  iter1 = 1..100.iterator;
  elem1;
  function next1() {
    elem1 = iter1.next();
    return elem1 != EOF;
  }
  function next2() {
    while (next1() AND !primo(elem1));
    return elem1 != EOF;
  }
  function next() { //Este es el que se invoca externamente
    if (next2()) {
      return elem1;
    }
    return EOF;
  }
}

Cada comprehensión genera un Iterator distinto, según las combinaciones de for's e if's que tenga. Como pueden ver, al pedir el siguiente elemento de este iterador, se invoca una función que hace el filtro, obteniendo el siguiente elemento del iterador interno inmediato anterior en la comprehensión y validando que el nuevo elemento cumpla la condición.

En contraste, las versiones de Groovy y Scala que usan map y filter, tienen un desempeño necesariamente peor en condiciones más complejas, como el ejemplo de las vocales en mayúsculas: primero se obtiene una palabra, luego se itera sobre los caracteres de dicha palabra, filtrando las vocales; al final se tiene una colección de vocales; con la siguiente palabra se obtiene otra colección de vocales y por eso hay que hacer flatten, para que se junte todo en una sola colección.

Con esta implementación de comprehensiones en Ceylon, primero se llama una función interna next3 que invoca una función next2 que invoca una función next1 y con eso al final ya se obtuvo un caracter; luego se revisa si el caracter es vocal y de ser así se devuelve; de lo contrario se avanza sobre next3, que irá invocando a next2 y next1 conforme se necesite avanzar sobre las otras colecciones, para ir obteniendo los elementos necesarios y devolverlos. Ni siquiera se genera una nueva colección, aunque obviamente se puede crear una colección a partir de la comprehensión, dado que es un Iterable y esta interfaz implementa el método sequenced, que devuelve ya una secuencia con los elementos obtenidos del Iterable - esto gracias a que las interfaces en Ceylon pueden tener métodos concretos.

Comentarios

Opciones de visualización de comentarios

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

Se ve muy interesante el lenguaje

La nueva versión del lenguaje (0.2) a mi parecer es más estable, ya no tuve ningún problema al instalarlo.

void run(){
String[]  palabras={"hola", "desde un ", " programa escrito en", "Ceylon"};
for(p in palabras){
  print (p.reversed);
}

}

Buen post como siempre.

Imagen de ezamudio

qué bien

Las comprehensiones vendrá en M3; M2 aún no las incluye. De hecho es una cosa bastante nueva, Gavin tenía ganas de implementarla desde hace tiempo pero no se llegaba a un acuerdo en la sintaxis. En cuanto todo mundo estuvo de acuerdo con algunos cambios que había que hacer a la spec (porque esto requirió un par de cambios en una que otra definición del lenguaje), lo implementó en el verificador de tipos y yo lo implementé en ceylon-js; espero que pronto lo podamos poner en try.ceylon-lang.org para que lo puedan probar.

La diferencia entre una comprehensión y un for "normal" como el que pusiste en tu comentario, es que ese for es un ciclo, solamente ejecuta el bloque de código una vez por cada elemento obtenido del iterador; en cambio, una comprehensión resulta en un iterador, el cual puedes usar posteriormente en un for normal, aunque la intención es poder usarlas en invocaciones de métodos y funciones que esperan recibir un argumento secuenciado. Por ejemplo:

//La función entries está definida como entries(Element... seq)
entries(for (i in 1..100) if (i%5==0) i);
//Lo anterior es lo mismo que invocar esto:
entries(5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100);
//O puedes hacer esto:
value cincos = { for (i in 1..100) if (i%5==0) i }; //esto se vuelve un Iterable<Integer>
for (i in cincos) {
  print(i*1000);
}

La diferencia visualmente es bastante obvia: el for normal requiere un bloque de código, mientras que una comprehensión recibe una sola expresión (que puede ser un if, otro for de comprehensión, o una expresión normal).

Imagen de Sr. Negativo

Comprehensiones en Ceylon

Por lo que he leído en tu blog y en la página oficial del lenguaje se ve que vienen más cambios.

En Python sería algo así:

vector=[5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100]
print [x*1000 for x in vector  if (x%5==0)]
Imagen de Jose Manuel

No me termina de quedar claro

No me termina de quedar claro que es una comprehension. Incluso el termino nunca lo había leído... Alguien lo puede explicar en español de la gente normal :P

Por cierto, de tanto nombrar a Ceylon me dan ganas de entrarle al lenguaje.

Imagen de ezamudio

Python

Así es, es exactamente lo mismo, solamente que en Ceylon pones la expresión al último (aunque estrictamente hablando, la notación de Python es más cercana a la notación matemática original).

Jose Manuel, lo de comprehensiones está basado en los conceptos de teoría de conjuntos, cosa que viste en matemáticas no recuerdo si en secundaria o prepa. En Wikipedia viene una buena descripción (aunque está en inglés). En español de la gente normal no sabría cómo explicarlo porque la gente normal no pone atención en clases de matemáticas porque piensan que es aburrido, que no sirve para nada y que eso es de nerds y por eso y otras cosas la gente normal tampoco programa. Pero para un programador lo podría explicar simplemente como definir una colección a partir de otra colección (o varias).

Para definir una colección de los números pares del 1 al 100 simplemente escribes for (i in 1..100) if (i%2==0) i, en vez de tener que escribir 2, 4, 6, 8, 10..., etc. Si tienes una lista de cadenas y quieres obtener las que empiezan con mayúscula simplemente escribes for (s in cadenas) if (exists p=s[0]) if (p.uppercase) s. El cuadrado de los múltiplos de 3 del 1 al 100 lo obtienes con for (x in 1..100) if (x%3==0) x*x.

Espero que con eso quede claro el concepto básico. Pero con comprehensiones puedes hacer todavía más cosas, porque puedes anidarlas, ya sea que tengas una colección de colecciones o que uses colecciones que no estén relacionadas entre sí.

Imagen de Jose Manuel

Creo que dedico mucho tiempo

Creo que dedico mucho tiempo en ver películas, de allí la frase, de español de la gente normal. Osea, fue una broma :)
Teoría de conjuntos no me suena ni de la secundaria ni de la preparatoria, me suena de matemáticas discretas en mi primer semestre de la universidad.

Entonces tomare como definición: Definir una colección a partir de una o mas colecciones. Ahora, ¿Que tipos de datos pueden formar parte de estas colecciones? -Ya observe que usas cadenas y enteros. ¿Pueden ser cualquier objeto? ¿Los tipos de datos son definidos por el usuario o por el lenguaje?
Son preguntas algo tontas pero yo así le entiendo mejor. A la mejor para encontrar la respuesta basta revisar las características del lenguaje y/o la documentación...

Saludos! :)

Alguna vez te sonó aquello

Alguna vez te sonó aquello de:

El conjunto formado por los números blah, blah donde X sea mayor que 10 y X sea menor que 1000, etc. etc

Esa es una forma de describir que elementos están contenidos en un conjunto , por ejemplo si son pares, si son nones, si son cada tercer número, si son solo los que sean divisibles entre tal o cual etc.

Un ejemplo es este:

"S" es el conjunto definido por dos veces X donde X pertenezca al conjunto de los naturales y x sea menor o igual a 10

Entonces se entiende mejor aquello de "crear un conjunto a partir de otro" de una forma más clara.

En los lenguajes de programación funcional hay algo parecido que se llama comprehensiones de listas, porque estaría muy difícil ( creo ) definir algo tan abstracto como el conjunto de los numeros naturales.

Por ejemplo en Haskell que es uno de los lenguajes de programación funcional más representativos, lo anterior se escribiría así:

[x*2 | x <- [1..10]]

Muy similar, no? Y se lee casi igual, la lista formada por x por 2 donde x sea un elemento del la lista del 1 al 10

Los lenguajes de programación funcional los usan porque sus principales elementos de construcción son las funciones ( como en las matemáticas ) y pues aprovechan para tener cosas de las matemáticas como estas.

En Ceylon esta misma definición sería:

for ( x in 1..10 ) x * 2;

Y pues como Ezamudio lo dijo, aunque Ceylon es un lenguaje de programación orientado a objetos , se incluyen porque es cool :)

En un lenguaje de programación puro ( como Smalltalk, Ruby o Ryz :P ) lo mismo se lograría hacer con un método que recopile elementos de un objeto.

Ejemplo:

1.to(10).collect( (x){ x .* 2 } )

Y en Java se podría escribir así:

collect(  Arrays.asList(1,2,3,4,5,6,7,8,9,10 ), new Predicado() { public int run( int x ) { return x * 2 ; });

...
public List<Integer> collect( List<Integer> lista, Predicado predicado ) {
    List<Integer> resultado = new ArrayList<>();
    for( int i : lista ) {
     resultado.add( predicado.run(  i ) );
    }
   return resultado;
}
...
interface Predicado {
   public int run( int x );
}

¬¬

Imagen de Sr. Negativo

Re:No me termina de quedar claro

@JoseManuel
Como lo explico @ezamudio esta característica todavía no se incluye en el lenguaje, en la página oficial del lenguaje hay varios post sobre las características del lenguaje y de lo que vendrá mas adelante.

Imagen de ezamudio

puro?

En un lenguaje de programación puro ( como Smalltalk, Ruby o Ryz :P )

Ah caray y entonces Ceylon es "impuro"? hay alguna razón técnica para esto o es un atributo puramente religioso?

Oscar: Con los típicos métodos map y filter puedes obtener los mismos resultados, sin embargo como expliqué en el post, es mucho mejor el performance cuando usas comprehensiones que si usas esos métodos (aunque me parece que también los vamos a incluir, probablemente en la interfaz Iterable, aunque la verdad ya teniendo comprehensiones yo me doy por bien servido con tener find y fold).

Jose Manuel: a poco nunca viste en secu o prepa los diagramas de Venn Euler? las uniones e intersecciones de conjuntos? Eso es teoría de conjuntos también, pero explicada (literalmente) con palitos y bolitas. En mate discretas ya ves cosas más avanzadas del mismo tema como productos y cosas así, usando la notación que menciona Oscar.

Sr. Negativo: por el momento los proyectos involucrados tienen una rama aparte con la implementación de las comprehensiones, que será integrada posteriormente a la principal.

Si, como JavaSeeehhhh sangre

Si, como Java

Seeehhhh sangre sucia... jo jo jo

¬¬

Ok, no ya en serio.

Si, el criterio son básicamente los paradigmas que se utilizan para la construcción de los programas. En todos se pueden construir las mismas cosas ( en términos generales por supuesto ) y generalmente se utilizan varios paradigmas en el diseño de los lenguajes ( aunque esto suene contradictorio por sí mismo) .

La pureza de un lenguaje, o al menos la pureza en cuanto a un paradigma, radíca en la cantidad de paradigmas que utiliza. Por ejemplo Smalltalk aunque tiene algo que se parece muchisimo a los closures no se les llamó como así, se les llama "bloques de código" y su uso de objetos llega a tal grado que incluso no hay if's, for's o while's ( que vienen del paradigma imperativo ) sino que a los objetos se les invocan métodos que reemplazan esta funcionalidad.

Por ejemplo la clase Bool tiene el método ifTrue que recibe un bloque de código a ejecutar. Tiene dos subclases True y False y estas tienen sobreescrito este método. La subclase True ejecuta el bloque de código y la clase False no le hace caso ( también tiene un ifFalse que funciona al revés )

Entonces, lo que en Java se escribiría así:

if ( 1 > 2 ) {    System.out.println("El mundo va a colapsar"); }

En smalltalk se escribe así:

1 > 2 ifTrue:[  Transcript show: 'El mundo va a colapsar' ].

Donde el objeto 1 con el método > recibe otro objeto 2 y regresa un objeto de tipo Bool ( especificamente esta regresando la subclase False ) al cual se le invoca el método ifTrue que a su vez recibe el parametro bloque que dentro tiene .... etc.

Entonces, si un lenguaje tiene varios paradigmas y además esos forma parte esencial del mismo , pues menos "puro" será de uno solo no? En todo caso se puede crear un nuevo paradigma para devolverle la puresa, como por ejemplo en el caso de Scala han querido llamarle "Object-Functional".

Y pues ya.

Imagen de echan

Aunque en Scala para alinear

Aunque en Scala para alinear colecciones es mas comun usar flatMap que usar por separado map y flatten :

List("hola", "mundo").flatMap(  _.toUpperCase )

se lee mejor y es aquivalente a

List("hola", "mundo").map { _.map(_.toUpper) } flatten

respecto al ejemplo del for se puede compactar mas al eliminar la segunda extraccion porque la coleccion ya se alineo ( en Scala las comprehensiones for no son mas que azucar sintactico del flatMap y filter).

for(w <- List("hola","mundo"); c <- "aeiou" if w.contains(c)) print(c.toUpper)

aunque esto creo que tambien aplica para Cylon

Imagen de ezamudio

No, no es lo mismo

aunque esto creo que tambien aplica para Cylon [sic]

OK creo que en Scala no lo escribí de la mejor manera. Esa es una de mis quejas contra el for en Scala, como que no está muy clara la manera de usarlo. En Ceylon también se podría voltear: for (w in {"hola","mundo"}) for (c in "aeiou") if (c in w) c.uppercased, el resultado será el mismo. Pero no Ceylon las comprehensiones no son azúcar sintáctica de flatMap y filter, son azúcar sintáctica para una construcción más compleja pero que da mucho mejor performance (lo explico en el mismo artículo). El problema que tiene usar solamente flatMap y filter es que se generan varias colecciones intermedias y se realizan las operaciones sobre dichas colecciones intermedias, por lo que si tienes filter/map/filter (por citar un ejemplo) se realiza primero el filter sobre la colección original y el resultado es una colección con menos elementos, la cual se pasa al map, que devuelve una colección del mismo tamaño pero con los elementos transformados, y eso se pasa al siguiente filter que devolverá una colección con menos elementos.

En Ceylon por la manera en que se genera el código para una comprehensión que sea de tipo for-if-if, obtendrás un Iterable y conforme le pidas elementos, se irán obteniendo de los iterables intermedios, sin generar listas intermedias (lo explico con el pseudocódigo).

De hecho apenas estoy implementando map/filter/fold/find en Iterable, pero las comprehensiones ya funcionaban desde antes de tener esos métodos porque simplemente no se utilizan, no son necesarios para procesarlas.

Fibonacci

Comprendiendo todo esto de comprehension pues si todo gira en torno a conjuntos, si esto es asi y como es bien sabido que las matematicas (TEORIA DE CONJUNTOS) son una fuerte base de la LOGICA DE PROGRAMACION; podria ser que las series de Fibonacci pudieran tomar gran fuerza con la comprehension??
Ya saben puesto que ya es una serie que define X elementos en una Y sucecion pues mi mente divago en cuanto a su utilizacion dentro de este marco. Tal vez me emocione demasiado ¬¬ pero pues uno nunca sabe no??
Ademas y los clousures junto con la comprehension .....

Imagen de echan

Lo que puedo ver es que en la

Lo que puedo ver es que en la superficie tanto Ceylon como Scala son muy similares a la hora de trabajar con las comprehensiones de listas... la diferencia hasta donde puedo ver, es que en el caso de Ceylon la evaluacion de una comprehension siempre es de forma lazy independientemente de la coleccion que se use ¿no?. En el caso de Scala la evaluacion depende del tipo de coleccion, por poner un ejemplo una comprehension hecha con Seq se evalua de forma estricta pero un Stream siempre es lazy.

Imagen de Jose Manuel

No me estoy enterando de todo

No me estoy enterando de todo pero se escucha muy bonito. Entonces la ventaja de usar comprehensiones es la posibilidad de crear estructuras complejas de manera simple ademas de ser mas claras visiblemente y de fácil escritura y que de manera particular en Ceylon son mas eficientes...

¿Ando bien? ¿Alguien podría poner un ejemplo (otro ademas de las cadenas y las funciones matemáticas) en el que usarían las comprehensiones? Algo que me ayude a entender ya un uso mas "natural", así con manzanitas y eso... si no es mucha lata claro.

Porque a mi se me viene a la mente un ejemplo donde de una lista de alumnos agregar un filtro y obtener aquellos que su primer apellido sea Hernandez. O el mismo ejemplo pero en lugar de que la información este contenida en una sola lista ahora estaría en 4. No se, ando muy corto de imaginación. Pero eso ya es parecido a lo de las cadenas... ya ven, mentes simples, ejemplos simples...
O de a tiro, ¿Mejor me voy y no regreso?

@OscarRyz neta que estoy viendo demasiadas películas, cuando leí sangre sucia se me vino a la mente la imagen de Draco Malfoy de HP... jaja.

Imagen de bferro

Un ejemplo para José Manuel

No hay mucha complicación para entender el concepto de "Lista por comprensión". Aunque la palabra suene algo extraña en español, lo que significa es relativamente fácil . Desconozco porque están usando en este post el término en inglés comprehension, cuando en español existe el término comprensión que viene del término comprender.
El término se acuña en la teoría de conjuntos. Un conjunto se puede definir de dos formas: enumerando de manera extensiva todos los miembros del conjunto o bien "comprendiendo" su significado y hacerlo de manera "intensional". Ese término "intensional" no existe en español, y es necesario distinguirlo de "intencional" que es otra cosa.
Al definirlo por comprensión, estableces las características o propiedades que deben cumplir los miembros del conjunto, en lugar de citarlos de manera explícita. Por ejemplo, el conjunto de los números naturales que son divisibles entre tres, en lugar de escribir 3,6,9, 12, ....
Partiendo de esa idea sobre conjuntos, los lenguajes de programación introducen construcciones para poder definir listas por comprensión (en general secuencias).
Esa construcción permite generar una lista a partir de una función definida parcialmente que se concreta mediante una expresión la cual especifica el dominio de partida y declara los valores comprendidos de la lista mediante una secuencia de generadores y filtros.
Que la función esté definida parcialmente quiere decir que únicamente está definida para un subconjunto del dominio de entrada. Los valores comprendidos que serán parte de la lista resultante, serán aquellos generados por los generadores y que cumplen con la condición que establece el filtro o los filtros. Esos filtros, no son más que predicados que se evalúan para cada miembro generado por el generador. Si el predicado o condición lógica se evalúa como cierto para cada elemento generado, ese elemento se incluirá en la lista resultante (es un valor comprendido). Si el predicado se evalúa a falso para el elemento, entonces NO será un valor comprendido y no formará parte de la lista resultante.
La idea es poder construir en un lenguaje de programación la notación de conjunto definido de manera intensional.
Un ejemplo sencillo: Expresar en el lenguaje de programación el subconjunto de los números naturales cuyos elementos son el doble de aquellos que cumplen que el valor de su cuadrado es mayor que 3
Lo escribo en Haskell:

s = [2 x | x < - [ 0 .. ] , x ^ 2 > 3]

¿Qué representa cada "cosa" en la expresión anterior?

  • 2 x (el primer término) representa la función de salida, o sea la expresión que será incluida en la lista resultante. En este caso esa función es igual al doble de los elementos generados por el generador que cumplen el predicado que sigue.
  • x ( la segunda x) representa la variable que será evaluada por el predicado.
  • < - [ 0 ... ] es el generador de la secuencia, en este caso un generador de números naturales.
  • x ^ 2 > 3 es el predicado o filtro, que evaluará cada variable. Si el resultado de la evaluación es verdadero, el valor será un valor comprendido, que se multiplicará por 2 y formará parte de la lista resultante. Si el resultado de la evaluación del predicado es falso, entonces NO será un valor comprendido y no formará parte de la lista resultante.

Un ejemplo sencillo en Scala (tomado del libro de Odersky)
Definimos una clase para representar personas con tres propiedades: su nombre, su sexo y sus hijos. Lo hacemos con una clase case para ahorrarnos código aunque eso para el ejemplo es irrelevante.

case class Person (name:String, isMale: Boolean, children: Person *)

Definimos ahora una familia con madre soltera

val lara = Person("Lara", false) // Una mujer sin hijos
val bob = Person("Bob", true) // Un hombre sin hijos
val julie= Person("Julie", false, lara, bob) //La madre de lara y bob
// La lista de esas tres personas
 val persons = List (lara, bob, julie)

Queremos ahora tener una lista de pares (tuplas de 2 elementos). Cada tupla está formada por el nombre de la madre y el nombre de uno de sus hijos. Queremos obtener el siguiente resultado:

List ( (Julie, Lara) , (Julie, Bob) )

Escribo el código con una expresión for para eso (la lista por comprensión):

for {
   p <- persons
   if !p.isMale
   c <-p.children
} yield (p.name, c.name)

Explico el código:

  • Podemos usar llaves o paréntesis para la expresión for. Con llaves se ve en ocasiones más claro y nos ahorramos los punto y coma(;).
  • La función de salida es yield (p.name, c.name) que significa incluir en la lista resultante los valores comprendidos producto de los generadores y los filtros o predicados aplicados a los valores generados.
  • Hay dos generadores, el primero genera un valor para la variable p, almacenando en ella a una persona. El segundo genera un valor para la variable c almacenando en ella un hijo. El segundo generador corre más rápido que el primero.
  • Las variable son p y c.

Pueden haber tantos generadores y predicados como se quieran y necesiten, siempre teniendo en cuenta que los generadores más a la derecha corren más rápido. Eso se puede ilustrar mejor con el ejemplo que se ha estado usando en este post algo modificado:

val a = for (w <-List ("hola", "mundo" ) ; c <- "aeiou" ; if ( w.contains (c) ) ) yield w
println(a)

El resultado será:

List (hola, hola, mundo, mundo)

Igual podría haberse explicado el concepto y el ejemplo en Ceylon, o en otro lenguaje.
Espero no haber confundido más.

PD. ¿Podría alguien explicarme que quiere decir ese término "alinear colecciones" en el contexto de listas por comprensión y que se ha usado aquí?

Imagen de echan

Desconozco porque están

Desconozco porque están usando en este post el término en inglés comprehension

buen punto .. la profesión está llena de anglicismos y muchas veces tendemos a usarlos indistintamente ... y francamente es bastante extraño y extenso expresar en castellano cosas como closures, callbacks, continuations, etc, etc.. ahora no se si sea una buena tendencia o no .. ¿ustedes que opinan? seria buena idea hacerle mas honor al "lenguaje"?.

Con respecto al otro punto ... < fail > alinear una coleccion < /fail > me referia a la operacion flatten o flatMap que empareja, asocia, unifica, etc un tipo de datos sobre otra que la encierra .. parece que en otros contextos el termino es bind o join y viene de los terrenos escabrosos de la teoria de las categorias ( monads ) .... en palabras simples dada una representacion concreta de una lista de lista de enteros obtengo una lista de enteros

List[List[Int]] -> flatten -> List[Int]

y dado que en el caso de Scala o Haskell las "Listas por comprensión" son operaciones "monadicas" lo que se realiza como parte del procesamiento
es la alineacion de las colecciones

Imagen de bferro

aplanar la colección, no alinearla

Flatten a collection: aplanar una colección

Mi problema con las comprensiones es...

el debug.
Poder escribir

.
for (w in { "hola", "mundo" }) for (c in w) c.uppercased;

es hermoso, el código java equivalente da asco en comparación.
Pero si hice algo mal en java, puedo debuguear y ver que pasa.
¿Cómo maneja el debug de estas cosas Ceylon?
Ya se que los programadores realmente buenos nunca hacen debug, pero para nosotros, los programadores promedio, es una herramienta necesaria.
¿que veo yo, como programador, cuando hago debug de esa línea?¿con que granularidad puedo poner los breakpoints?

Imagen de ezamudio

lineas

podrias separarlo en lineas:

for (w in {"hola", "mundo"})
  for (c in w)
    c.uppercased

A fin de cuentas, el código generado ya está probado y son simples iteradores en cascada (hasta me duele escribir eso porque me costó bastante trabajo implementarlos en js pero bueno, a fin de cuentas, eso son). Cuando estén implementados en Java, seguramente será algo similar, y la única manera en que veo que te da problemas ese código es porque estás invocando alguna función tuya (en vez de c.uppercased invoques una f(c) que hayas definido tú) y en ese caso los breakpoints van dentro de tu función, no importa que sea invocada desde una comprensión.

Ceylon aún no tiene debugger, de modo que no puedo contestar tu pregunta de cómo maneja estas cosas. Pero ciertamente es una buena oportunidad para recibir propuestas acerca de eso.

Imagen de Sr. Negativo

Ceylon M3.1

Ceylon M3.1 "V2000"
http://ceylon-lang.org/download/

Ya tiene tiempo que salió.

Imagen de ezamudio

una semana

Y en esa versión ya hay comprensiones tanto en JS como en JVM, además de que la interfaz Iterable tiene ya bastantes métodos muy útiles. Gavin escribió al respecto de eso en el blog oficial del lenguaje.

Ahí nomás me queda presumir que yo implementé las comprensiones en ambos backends, además de casi todos los métodos de Iterable: map, filter, fold, find, findLast, every, any, sorted (que acabo de renombrar como sort, pero pues eso lo tendrán para M4), indexed, coalesced, chain, etc.