Pipelines

El operador Pipeline es un operador especial de Elixir que sirve para hacer más fáciles de comprender algunas invocaciones a funciones, porque nos permite separar el primer parámetro de una aplicación del resto de parámetros, algo que nos viene bien porque podemos escribir expresiones tipo a(b(c(d(e(f(x))))) como x → f → e → d → c → b → a.

Cuando trabajamos con listas en Elixir, combinar funciones como filter, map y reduce nos permite hacer transformaciones potentes, pero a veces el código puede volverse difícil de leer y entender. Por ejemplo, imaginemos que tenemos una lista con los números del 1 al 9 y queremos obtener el doble de aquellos elementos que sean múltiplos de 2 o de 3, para luego sumar esos valores. Si intentamos encadenar estas funciones sin ninguna ayuda, el código puede resultar confuso y complicado de seguir, especialmente cuando anidamos llamadas como filter(map(lista, ...), ...) o reduce(filter(map(lista, ...), ...), ...).

Para resolver este problema, Elixir nos ofrece un operador muy útil llamado pipeline, que se escribe como |>. Este operador nos permite encadenar funciones de manera clara y ordenada, pasando el resultado de una expresión como primer parámetro de la siguiente función. Por ejemplo, en lugar de escribir:

reduce(
  map(
    filter(lista, fn x -> rem(x, 2) == 0 or rem(x, 3) == 0 end),
    fn x -> x * 2 end
  ),
  0,
  fn x, acc -> x + acc end
)

Podemos usar el pipeline para hacerlo mucho más legible:

lista
|> filter(fn x -> rem(x, 2) == 0 or rem(x, 3) == 0 end)
|> map(fn x -> x * 2 end)
|> reduce(0, fn x, acc -> x + acc end)

Aquí, el operador |> toma el valor de la izquierda y lo pasa como primer argumento a la función de la derecha. Si la función requiere más parámetros, los añadimos después. Esto hace que el código se lea como una cadena de transformaciones, donde cada paso recibe el resultado del anterior.

Además, podemos mejorar la legibilidad poniendo cada llamada en una línea separada, lo que facilita entender el flujo de datos y las transformaciones que aplicamos. Por ejemplo:

lista
|> filter(fn x -> rem(x, 2) == 0 or rem(x, 3) == 0 end)
|> map(fn x -> x * 2 end)
|> reduce(0, fn x, acc -> x + acc end)

Otra ventaja del pipeline es que podemos insertar inspecciones intermedias para ver cómo va cambiando el dato a lo largo de la cadena. Usando la función io.inspect/1, que imprime el valor recibido y lo devuelve sin modificarlo, podemos colocarla en cualquier punto del pipeline para depurar o entender mejor el proceso:

lista
|> filter(fn x -> rem(x, 2) == 0 or rem(x, 3) == 0 end)
|> io.inspect()
|> map(fn x -> x * 2 end)
|> io.inspect()
|> reduce(0, fn x, acc -> x + acc end)

Esto nos muestra el estado de la lista después de filtrar y después de mapear, sin interrumpir la cadena de transformaciones.

Es importante destacar que para que el pipeline funcione correctamente, las funciones que encadenamos deben tener como primer parámetro el dato que queremos transformar. Por eso, muchas funciones en Elixir están diseñadas con esta convención. Por ejemplo, Map.put/3 recibe primero el mapa, luego la clave y el valor; Map.keys/1 recibe el mapa como primer parámetro. Esto facilita que podamos encadenar llamadas con el operador pipeline sin complicaciones.

Veamos un ejemplo práctico con mapas:

%{}
|> Map.put(:a, 1)
|> Map.put(:b, 2)
|> Map.keys()
|> io.inspect()

Aquí empezamos con un mapa vacío, le añadimos dos claves con sus valores y luego obtenemos las claves, que finalmente imprimimos. Todo esto en una cadena clara y fácil de seguir.

Cuando escribimos código en archivos, podemos aprovechar la flexibilidad del pipeline para organizarlo con indentaciones y saltos de línea que mejoren la legibilidad. Por ejemplo:

lista
|> io.inspect(label: "Lista original")
|> map(fn x -> x + 2 end)
|> filter(fn x -> rem(x, 2) == 0 end)
|> io.inspect(label: "Después de map y filter")

Así, podemos ver claramente cada paso y entender cómo se transforma la información.

El uso del operador pipeline es especialmente común cuando trabajamos con librerías como Plug o Ecto, donde las transformaciones encadenadas son la norma. En estos contextos, el pipeline nos ayuda a escribir código más limpio y mantenible, evitando la confusión que puede generar el anidamiento excesivo de funciones.

En definitiva, el operador pipeline nos permite construir cadenas de transformaciones tan largas como necesitemos, manteniendo la claridad y facilitando la comprensión del flujo de datos en nuestros programas Elixir. Esto nos da mucha más soltura para escribir código complejo sin perdernos en paréntesis o llamadas anidadas.

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