Los lenguajes de programación funcionales, aunque cada uno se presenta como único, comparten características fundamentales que los hacen muy similares cuando los analizamos en profundidad. Uno de esos conceptos clave es el de las funciones de primera clase. En Elixir, esto significa que las funciones no son solo bloques de código aislados, sino que podemos tratarlas como cualquier otro dato: podemos asignarlas a variables, pasarlas como argumentos a otras funciones o devolverlas como resultados.
Por ejemplo, si tenemos una función como String.upcase/1, que transforma una cadena a mayúsculas, podemos capturarla usando el operador ampersand y asignarla a un identificador. Así, podemos hacer algo como:
mayusculas = &String.upcase/1
mayusculas.("hola") # Devuelve "HOLA"
Además, podemos definir funciones anónimas fácilmente. Por ejemplo, una función que duplique un número se puede escribir así:
doble = fn x -> x * 2 end
doble.(5) # Devuelve 10
Este manejo flexible de funciones nos lleva al siguiente concepto fundamental: las funciones de alto orden. Estas son funciones que reciben otras funciones como parámetros o que devuelven funciones. En Elixir, un ejemplo sencillo es la función apply/2, que nos permite aplicar una función a una lista de argumentos. Por ejemplo, si queremos aplicar la función doble a un valor, podemos hacerlo de dos maneras:
doble.(5) # Forma directa, devuelve 10
apply(doble, [5]) # Forma usando apply, también devuelve 10
La función apply es muy general y puede recibir funciones con diferentes números de parámetros, siempre que la lista de argumentos coincida con la aridad de la función. Por ejemplo, si capturamos la función rem/2 (que calcula el resto de una división), podemos usar apply para pasarle dos argumentos:
apply(&rem/2, [9, 2]) # Devuelve 1
Aunque apply es una función de alto orden muy versátil, en la práctica usamos otras funciones de alto orden que están diseñadas para trabajar con colecciones de datos, como listas, tuplas o mapas. En Elixir, estas funciones se encuentran en el módulo Enum y son herramientas esenciales para el día a día.
Entre ellas, map nos permite transformar cada elemento de una colección aplicándole una función. Por ejemplo, si queremos duplicar cada número de una lista, haríamos:
Enum.map([1, 2, 3], fn x -> x * 2 end) # Devuelve [2, 4, 6]
La función filter nos ayuda a seleccionar solo aquellos elementos que cumplen una condición. Por ejemplo, para quedarnos con los números pares de una lista:
Enum.filter([1, 2, 3, 4], fn x -> rem(x, 2) == 0 end) # Devuelve [2, 4]
Por último, reduce nos permite combinar todos los elementos de una colección en un único valor, aplicando una función acumulativa. Por ejemplo, para sumar todos los números de una lista:
Enum.reduce([1, 2, 3, 4], 0, fn x, acc -> x + acc end) # Devuelve 10
Estas funciones no son exclusivas de Elixir; muchos lenguajes funcionales y algunos imperativos también las incluyen, lo que facilita trabajar con colecciones de manera declarativa y elegante. Así, al dominar las funciones de primera clase y las funciones de alto orden, podemos escribir código más flexible, reutilizable y expresivo en Elixir.