Herranz

Lenguajes Funcionales. ¡Están entre nosotros!

En este artículo exploraré cómo los elementos esenciales de la programación funcional están cada vez más presentes en los lenguajes de programación imperativos, los más populares como Python, Java, Javascript, etc.

Además intentaré dar respuesta a una pregunta realmente interesante:

¿Se pueden usar los elementos de la programación funcional en lenguajes imperativos sin entender dichos conceptos dentro del paradigma funcional?

¿Es relevante la programación funcional en la industria?

La programación funcional no es una idea nueva. Desde hace décadas, ha sido muy relevante en contextos académicos, especialmente en universidades. En MIT, el lenguaje funcional Scheme y el clásico libro Structure and Interpretation of Computer Programs han sido la introducción fundamental a la programación durante muchos años.

A pesar del consenso académico y de que la industria está llena de casos de éxito en el uso de lenguajes funcionales (por ejemplo Whatsapp), la adopción de los mismos es mínima y se considera un nicho reservado para problemas muy específicos o para programadores e investigadores apasionados.

A tenor de los índices de popularidad de los lenguajes de programación, la respuesta es

No, los lenguajes de programación funcional no son relevantes en la industria.

Veamos lo que dicen ciertos rankings en enero de 2025:

Algo que contrasta con esta aparente falta de relevancia es la alta demanda de programadores expertos en programación funcional: los tres primeros lenguajes de programación mejor pagados son lenguajes funcionales: Erlang, Elxir y Clojure (Stackoverflow Developer Survey, 2024, https://survey.stackoverflow.co/2024/technology#top-paying-technologies ) o el hecho de que dos lenguajes funcionales (Elixir y Clojure) se encuentren entre los 5 más admirados (Stackoverflow Developer Survey, 2024, https://survey.stackoverflow.co/2024/technology#admired-and-desired ).

¿Porqué tanta gente habla de programación funcional?

Probablemente estás leyendo este post porque has oido hablar de ciertos elementos de la programación funcional que ya estás usando ya que tu lenguaje favorito ya tiene o ha adoptado esos elementos. Veamos a qué elementos me estoy refiriendo y cuál es su origen.

Polimorfismo paramétrico (genéricos)

Una gran parte de los lenguajes populares han incorporado lenguajes de tipos que permiten la definición de tipos paramétricos (genéricos). Veamos un ejemplo en Java en el que se define un método de clase que invierte una lista con elementos de un tipo cualquiera.:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import java.util.*;
public class ListReverser {
    public static <T> List<T> reverse(List<T> list) {
        List<T> reversed = new ArrayList<>();
        for (int i = list.size() - 1; i >= 0; i--) {
            reversed.add(list.get(i));
        }
        return reversed;
    }
}

Los tipos paramétricos se introducen por primera vez en el lenguaje funcional ML, en el año 1975.

Orden superior

Quizás sea el elemento de la programación funcional más aceptado entre los desarrolladores de lenguajes imperativos: funciones que reciben funciones como argumentos e incluso que devuelven funciones como resultado. ¿Qué lenguaje actualmente no tiene maps, reduces, lambdas, etc.? Veamos un ejemplo de uso del orden superior para inyectar una dependencia. En este caso la dependencia es una función que sabe buscar en un repositorio, ya sea en una base de datos o, por ejemplo, a través de un API REST. En C++ esta vez:

1
2
3
4
5
6
7
user_t get(int id, const std::function<user_t(int)>& repository) {
    try {
        user_t user = repository(id);
        return user;
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << "\n";
    }

El parámetro repository será una función que sabe extraer un usuario dado su identificador, cuando se llama a get se puede pasar el repositorio que se desee.

Todos los lenguajes de programación funcional incluyen el orden superior, por datarlo: desde 1936 con la invención del cálculo lambda.

Encaje de patrones

Otro elemento que nace en el seno de un lenguaje funcional son los tipos algebraicos y el encaje de patrones (pattern matching), esta vez el lenguaje es Hope también en la década 1970s.

Cada vez más lenguajes, de forma más o menos elegante, incorporan el encaje de patrones para descomponer datos en partes como se puede ver en este código en Javascript:

1
2
3
4
5
6
7
8
function area(shape) {
    const { type, ...props } = shape;

    if (type === 'circle') return Math.PI * props.radius ** 2;
    if (type === 'rectangle') return props.width * props.height;

    throw new Error('Unknown shape');
}

Sólo los diccionarios con atributo type encajan en la asignación de shape en la línea 2. Una adopción mucho más fuerte la hace Rust, el mismo código sigue un encaje de patrones más funcional:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
enum Shape {
    Circle(f64),
    Rectangle(f64, f64),
}

fn area(shape: Shape) -> f64 {
    match shape {
        Shape::Circle(radius) => 3.14 * radius * radius,
        Shape::Rectangle(width, height) => width * height,
    }
}

Inmutabilidad / transparencia referencial

El modelo de ejecución de los lenguajes imperativos se apoya intrínsicamente en la mutabilidad de un estado global. Los efectos secundarios son importantes: acoplamientos inesperados entre componentes, errores difíciles de depurar y pérdida de transparencia referencial (la misma entrada no produce la misma salida en ejecuciones diferentes). Para evitar estos problemas, cada vez son más las bibliotecas en lenguajes populares que introducen estructuras de datos inmutables. Veamos un ejemplo de uso en Python:

1
2
3
4
5
6
7
from immutables import Map

map = Map(a=1, b=2)
nuevo = map.set('c', 3)

print("Map:", map)
print("Nuevo:", nuevo)

Este esfuerzo por crear bibliotecas como immutables (¡mismo nombre en Java!) o incluso diseñar lenguajes como Rust para controlar el alcance de la mutabilidad es enorme. De nuevo, podría remitirme a la invención del cálculo lambda en 1936: la inmutabilidad es una característica intrínseca de los lenguajes funcionales.

¿Debería aprender un lenguaje funcional?

La mayor parte de los programadores, ya sean senior o junior, han aprendido a programar con lenguajes imperativos. Todos empiezan a oir o han oido hablar de genéricos, de órden superior y funciones anónimas, de inmutabilidad e incluso de encaje de patrones. ¿Pueden aprenderse estos elementos dentro del paradigma de la programación imperativa? Mi respuesta a esta pregunta es: No.

El modelo computacional del paradigma imperativo es radicalmente diferente del paradigma funcional. El resultado es que la introducción de los elementos de la programación funcional en los lenguajes más populares no es orgánica y por eso su aprendizajes es tan difícil.

Mi conclusión es que aprender un lenguaje funcional es primordial para entender esos elementos y mi recomendación es que los equipos exploren algún lenguaje funcional.

¿Cuáles son los desafíos?

Para aquellos equipos que ya están explorando algún lenguaje funcional o para aquellos que se deciden a hacerlo, desde este post, me gustaría advertir sobre los desafíos principales: