La composición conecta funciones en cadena. Permite alimentar una función utilizando los resultados de otra, modelando flujos lógicos secuenciales tanto en álgebra como en desarrollo de software.
En programación y matemáticas discretas, rara vez trabajamos con una única función aislada. Lo habitual es estructurar los programas en pequeños bloques simples y luego encadenarlos para resolver problemas complejos.
La **composición de funciones** es la operación matemática que formaliza este encadenamiento: consiste en aplicar una función al resultado de otra. Es el pilar fundamental de paradigmas de programación como la **programación funcional**, donde el desarrollo se basa en componer funciones puras.
Sean dos funciones f : A → B y g : B → C. La composición de g con f (denotada como g ∘ f, que se lee "g compuesta con f" o "g después de f") es una nueva función que va directamente desde A hasta C (g ∘ f : A → C) definida por:
Observa el orden de aplicación: **f se aplica primero** sobre la entrada x, y luego **g se aplica sobre el resultado** f(x). Es por esto que en la notación (g ∘ f)(x), la función f está más cerca de la variable independiente x.
Para poder componer dos funciones g ∘ f, es requisito indispensable que las salidas de la primera función quepan dentro de las entradas permitidas para la segunda. Formalmente, **la imagen de f debe ser un subconjunto del dominio de g**:
Si la función f produce una salida que la función g no sabe procesar (porque está fuera de su dominio), la composición no estará definida para esa entrada, produciendo un error de compatibilidad lógica o una excepción en el código.
El simulador interactivo representa tres conjuntos: A, B y C. Selecciona diferentes funciones y haz clic en **Calcular Composición (g ∘ f)** para ver la animación secuencial de cómo viajan los datos. Observa cómo la composición dibuja un camino directo (en morado y discontinuo) que va desde la entrada en A hasta el resultado final en C, omitiendo el paso intermedio B.
La composición de funciones tiene propiedades algebraicas que dictan cómo operarla:
En JavaScript, podemos implementar la composición combinando llamadas de funciones simples, o creando una función helper de alto nivel para componer funciones de forma dinámica. Este concepto es central en bibliotecas de programación funcional como Lodash (con _.flow) o Ramda.
// Función f(x) = x + 1
const f = (x) => x + 1;
// Función g(x) = x * 2
const g = (x) => x * 2;
// Composición manual: (g ∘ f)(x) = g(f(x))
const g_o_f = (x) => g(f(x));
// Composición genérica para múltiples funciones (de derecha a izquierda)
const compose = (...funcs) => (initialVal) =>
funcs.reduceRight((val, fn) => fn(val), initialVal);
// Crear la función compuesta dinámicamente
const compuestaDinamica = compose(g, f);
const dominio = [1, 2, 3, 4];
console.log("Evaluación Manual (g ∘ f):");
dominio.forEach(x => console.log(`x: ${x} -> (g ∘ f): ${g_o_f(x)}`));
console.log("Evaluación Dinámica (compose):");
dominio.forEach(x => console.log(`x: ${x} -> compuesta: ${compuestaDinamica(x)}`));
// Evaluación Manual (g ∘ f):
// x: 1 -> (g ∘ f): 4
// x: 2 -> (g ∘ f): 6
// x: 3 -> (g ∘ f): 8
// x: 4 -> (g ∘ f): 10
// Evaluación Dinámica (compose):
// x: 1 -> compuesta: 4
// x: 2 -> compuesta: 6
// x: 3 -> compuesta: 8
// x: 4 -> compuesta: 10
La composición de funciones demuestra cómo operaciones simples pueden combinarse para formar lógica robusta. Esta idea se refleja en la modularidad del desarrollo de software: tuberías (pipes) en Unix, encadenamiento de middlewares en servidores web o flujos funcionales de procesamiento de datos. Una vez dominado el encadenamiento de funciones independientes, podemos explorar funciones que se llaman a sí mismas sobre sus propios resultados anteriores.
En el próximo tema estudiaremos las **funciones recursivas básicas**, analizando cómo una función puede definirse en términos de sí misma para resolver problemas repetitivos mediante un caso base y un paso inductivo.