Las listas por comprensión son formas de crear o modificar listas. También hacen que los programas sean breves y fáciles de entender en comparación con otras formas de manipular listas. Se basa en la idea de notación de conjuntos; Si alguna vez has tomado clases de matemáticas con teoría de conjuntos o si alguna vez has estudiado notación matemática, probablemente sepas cómo funciona. La notación de conjuntos básicamente le dice cómo construir un conjunto especificando las propiedades que deben satisfacer sus miembros. La comprensión de las listas puede ser difícil de entender al principio, pero vale la pena el esfuerzo.
Un ejemplo de notación de conjuntos sería {x ∈ ℜ x = x^2}. Esa notación de conjuntos le indica que los resultados que desea serán todos números reales iguales a su propio cuadrado. El resultado de ese conjunto sería {0,1}. Otro ejemplo de notación de conjuntos, más simple y abreviado sería {x : x > 0}. Aquí, lo que queremos son todos los números donde x > 0.
Las listas por comprensión en Erlang tratan de construir conjuntos a partir de otros conjuntos. Dado el conjunto {2n : n en L} donde L es la lista [1,2,3,4], la implementación de Erlang sería:
1> [2*N || N <- [1,2,3,4]].
[2,4,6,8]
Si comparamos la notación matemática con la de Erlang y no hay mucho que cambie: las llaves ({}) se convierten en corchetes ([]), los dos puntos (:) se convierten en dos barras verticales (||) y la palabra 'in' se convierte en la flecha. (<-). Sólo cambiamos símbolos y mantenemos la misma lógica. En el ejemplo anterior, cada valor de [1,2,3,4] coincide secuencialmente con el patrón N. La flecha actúa exactamente como el operador =, con la excepción de que no genera excepciones.
También puede agregar restricciones a la comprensión de una lista mediante operaciones que devuelvan valores booleanos. Si quisiéramos todos los números pares del uno al diez, podríamos escribir algo como:
2> [X || X <- [1,2,3,4,5,6,7,8,9,10], X rem 2 =:= 0].
[2,4,6,8,10]
Donde X rem 2 =:= 0 comprueba si un número es par. Las aplicaciones prácticas surgen cuando decidimos que queremos aplicar una función a cada elemento de una lista, obligándolo a respetar restricciones, etc. Como ejemplo, digamos que somos dueños de un restaurante. Un cliente entra, ve nuestro menú y pregunta si podría tener los precios de todos los artículos que cuestan entre $3 y $10 con los impuestos (digamos 7%) contados después.
3> RestaurantMenu = [{steak, 5.99}, {beer, 3.99}, {poutine, 3.50}, {kitten, 20.99}, {water, 0.00}].
[{steak,5.99},
{beer,3.99},
{poutine,3.5},
{kitten,20.99},
{water,0.0}]
4> [{Item, Price*1.07} || {Item, Price} <- RestaurantMenu, Price >= 3, Price =< 10].
[{steak,6.409300000000001},{beer,4.2693},{poutine,3.745}]
Por supuesto, los decimales no están redondeados de manera legible, pero entiendes el punto. Por lo tanto, la receta para la comprensión de listas en Erlang es NewList = [Expression || Patrón <- Lista, Condición1, Condición2, ... CondiciónN]. La parte Patrón <- Lista se denomina expresión Generadora.
5> [X+Y || X <- [1,2], Y <- [2,3]].
[3,4,4,5]
Esto ejecuta las operaciones 1+2, 1+3, 2+2, 2+3. Entonces, si desea que la receta de comprensión de la lista sea más genérica, obtendrá: NewList = [Expression || GeneradorExp1, GeneradorExp2, ..., GeneradorExpN, Condición1, Condición2, ... CondiciónM]. Tenga en cuenta que las expresiones del generador junto con la coincidencia de patrones también actúan como filtro:
6> Weather = [{toronto, rain}, {montreal, storms}, {london, fog},
6> {paris, sun}, {boston, fog}, {vancouver, snow}].
[{toronto,rain},
{montreal,storms},
{london,fog},
{paris,sun},
{boston,fog},
{vancouver,snow}]
7> FoggyPlaces = [X || {X, fog} <- Weather].
[london,boston]
Si un elemento de la lista 'Clima' no coincide con el patrón {X, niebla}, simplemente se ignora en la comprensión de la lista, mientras que el operador = habría generado una excepción.