31. Composición de funciones

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.

31.1 Introducción

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.

31.2 Definición Formal

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:

(g ∘ f)(x) = g(f(x)) para todo x ∈ A

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.

31.3 Condición de Compatibilidad

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**:

Im(f) ⊆ Dom(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.

31.4 Simulador: Trazado de Composición

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.

Visualizador de Caminos (g ∘ f)
Presiona el botón para animar la evaluación de (g ∘ f)(x) = g(f(x)).

31.5 Propiedades de la Composición

La composición de funciones tiene propiedades algebraicas que dictan cómo operarla:

  • No es Conmutativa: En general, el orden de los factores altera el producto. g ∘ f ≠ f ∘ g.
    Ejemplo: Si f(x) = x + 1 y g(x) = x²:
    • (g ∘ f)(x) = g(x + 1) = (x + 1)² = x² + 2x + 1
    • (f ∘ g)(x) = f(x²) = x² + 1
    Dado que las fórmulas resultantes son distintas, queda claro que componer en un orden u otro produce resultados completamente diferentes.
  • Es Asociativa: Si tenemos tres funciones compatibles, el agrupamiento no altera el resultado final: **(h ∘ g) ∘ f = h ∘ (g ∘ f)**.
  • Elemento Identidad: Componer cualquier función con la función identidad I no altera la función: **f ∘ I_A = f** e **I_B ∘ f = f**.
  • Inversión de la composición: Al revertir una composición, el orden de las inversas también se invierte: **(g ∘ f)⁻¹ = f⁻¹ ∘ g⁻¹**.

31.6 Composición en Programación

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

31.7 Errores Comunes

  • Componer en orden inverso: Confundir g ∘ f (aplicar primero f, luego g) con aplicar primero g y luego f. Recuerda que la función de la derecha es la que recibe el parámetro inicial.
  • Ignorar incompatibilidades de dominio/imagen: Intentar componer funciones sin verificar si las salidas de la primera pertenecen al dominio de la segunda (por ejemplo, componer una función que devuelva números negativos con una función que calcule raíces cuadradas reales).

31.8 Qué debes recordar de este tema

  • La composición de funciones encadena la salida de una como entrada de la otra: (g ∘ f)(x) = g(f(x)).
  • La función de la derecha **(f)** es la primera en ejecutarse.
  • Es compatible únicamente si **Im(f) ⊆ Dom(g)**.
  • La composición **no es conmutativa**: en casi todos los casos, g ∘ f ≠ f ∘ g.
  • En programación, equivale a encadenar llamadas de funciones o a utilizar helpers de composición.

31.9 Conclusión

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.