Rangos y Streams

A diferencia de Enum.map y Enum.filter, con Stream.map y Stream.filter podemos obtener Streams. Un Stream en Elixir permite hacer procesamiento pospuesto: Elixir sabe que quiero procesar una colección con una función, pero no ejecuta el cómputo hasta que no haga falta, algo que en algunas ocasiones es ventajoso para hacer programas más eficientes.

En Elixir, cuando necesitamos trabajar con listas de números consecutivos, los rangos se convierten en una herramienta fundamental que nos ahorra mucho trabajo. En lugar de escribir manualmente una lista como [1, 2, 3, 4, 5], podemos definir un rango con una sintaxis muy sencilla: simplemente ponemos el número inicial, seguido de dos puntos .., y luego el número final. Por ejemplo, 1..15 representa todos los números del 1 al 15 de forma automática y eficiente.

Este rango no es simplemente una lista, sino una estructura especial que Elixir maneja internamente. Podemos usarla directamente con funciones del módulo Enum, como Enum.map, Enum.sum o Enum.filter, para procesar los números que contiene. Por ejemplo, si queremos sumar todos los números del 1 al 15, basta con hacer:

Enum.sum(1..15)

Y Elixir nos devolverá el resultado sin necesidad de construir la lista manualmente. También podemos filtrar los números pares con:

Enum.filter(1..15, fn x -> rem(x, 2) == 0 end)

Esto nos devuelve solo los números pares dentro del rango.

Sin embargo, hay un detalle importante sobre cómo funcionan estas funciones de Enum. Son colecciones eager, es decir, que procesan todos los elementos de la colección antes de pasar al siguiente paso. Por ejemplo, si hacemos dos Enum.map consecutivos con una función que imprime cada elemento, veremos que primero se procesan todos los elementos del primer map y luego todos los del segundo. Esto significa que si tenemos una lista muy grande, como un rango de millones de números, el procesamiento completo puede ser muy costoso y lento.

Para ilustrar esto, imaginemos que queremos encontrar el primer número par en un rango muy grande usando Enum.find. Aunque el número que buscamos esté cerca del principio, Enum.find tendrá que procesar toda la colección antes de devolver el resultado, porque las funciones eager no liberan resultados parciales hasta completar todo el procesamiento.

Aquí es donde entran en juego las colecciones lazy, o vagas, que en Elixir se manejan con Stream. Un Stream no procesa los elementos inmediatamente, sino que espera hasta que sea estrictamente necesario. Por ejemplo, si usamos Stream.map sobre un rango, no se ejecuta ninguna transformación hasta que convertimos el stream en una lista o usamos una función que consume elementos, como Enum.take.

Veamos un ejemplo:

stream = 1..100 |> Stream.map(fn x -> IO.inspect(x); x * 2 end)
Enum.take(stream, 5)

En este caso, solo se procesan los primeros cinco elementos del rango, y el resto queda sin evaluar. Esto es muy eficiente cuando trabajamos con colecciones muy grandes y solo necesitamos una parte de los datos o queremos evitar cálculos innecesarios.

Las funciones de Stream como map y filter devuelven streams que se pueden encadenar sin que se ejecute el procesamiento hasta que se consume el stream. Esto nos permite construir pipelines complejas que solo se ejecutan cuando realmente necesitamos los resultados, ahorrando tiempo y recursos.

En resumen, los rangos en Elixir nos facilitan la creación de listas de números consecutivos, y combinados con Enum podemos procesarlos fácilmente. Pero cuando trabajamos con grandes cantidades de datos o queremos optimizar el rendimiento, usar Stream para crear colecciones lazy es una estrategia muy poderosa que nos permite procesar solo lo necesario y mejorar la eficiencia de nuestros programas.

Lista de reproducción
  1. 1
    ¿Qué es Elixir?
    10 minutos
  2. 2
    Instalación de Elixir
    9 minutos
  3. 3
    ¿Qué es la programación funcional? (Como la de Elixir)
    20 minutos
  4. 4
    ¿Cómo funciona la REPL de Elixir?
    7 minutos
  5. 5
    ¿Cómo hacer asignaciones en Elixir?
    7 minutos
  6. 6
    Operadores aritméticos básicos
    6 minutos
  7. 7
    ¿Qué son los tipos de datos de Elixir?
    5 minutos
  8. 8
    Átomos en Elixir
    4 minutos
  9. 9
    Las palabras clave nil, true y false
    6 minutos
  10. 10
    Operadores lógicos de comparación
    8 minutos
  11. 11
    Comparación estricta con ===
    3 minutos
  12. 12
    Operadores lógicos y proposicionales
    8 minutos
  13. 13
    Invocación de funciones
    10 minutos
  14. 14
    Fundamentos de funciones
    9 minutos
  15. 15
    Cadenas de caracteres
    8 minutos
  16. 16
    Entrada y salida estandar de la mano de gets y puts
    9 minutos
  17. 17
    Concatenar e interpolar strings
    9 minutos
  18. 18
    Código fuente en archivos
    9 minutos
  19. 19
    Condicional IF y bloques DO-END
    11 minutos
  20. 20
    IFs anidados, UNLESS y COND
    12 minutos
  21. 21
    Definición de funciones
    11 minutos
  22. 22
    Fundamentos de compilación de módulos
    6 minutos
  23. 23
    Guardas
    8 minutos
  24. 24
    Funciones anónimas
    7 minutos
  25. 25
    Capturar funciones
    4 minutos
  26. 26
    Invocación de funciones dentro del mismo módulo
    7 minutos
  27. 27
    Tuplas
    8 minutos
  28. 28
    Introducción al pattern matching
    8 minutos
  29. 29
    Pattern matching en funciones
    11 minutos
  30. 30
    Las tuplas :ok, :error
    7 minutos
  31. 31
    case
    10 minutos
  32. 32
    Operador pin
    7 minutos
  33. 33
    Pattern matchings y recursividad
    5 minutos
  34. 34
    Listas
    9 minutos
  35. 35
    Operadores y funciones de lista
    10 minutos
  36. 36
    Keyword lists: listas de palabras clave
    8 minutos
  37. 37
    Mapas
    7 minutos
  38. 38
    Pattern matching de mapas y keyword lists
    6 minutos
  39. 39
    Operadores y funciones para mapas y keyword lists
    5 minutos
  40. 40
    Estructuras con defstruct
    11 minutos
  41. 41
    Bitstrings
    11 minutos
  42. 42
    Charlists
    10 minutos
  43. 43
    Funciones de alto orden en Elixir
    5 minutos
  44. 44
    Uso de la función filter
    10 minutos
  45. 45
    Uso de la función map
    7 minutos
  46. 46
    Uso de la función reduce
    9 minutos
  47. 47
    Pipelines
    11 minutos
  48. 48
    Rangos y Streams
    11 minutos
  49. 49
    Funciones recursivas con listas
    14 minutos