Componer funciones consiste en usar la salida de una función como entrada de otra para construir transformaciones más complejas.
La composición de funciones permite encadenar procesos. Primero se aplica una función y luego se usa su resultado como entrada de otra.
En programación, esta idea aparece constantemente: validar datos, normalizarlos, transformarlos, formatearlos y mostrarlos son pasos que pueden verse como funciones compuestas.
Si tenemos dos funciones f y g, la composición de f con g se escribe así:
Primero se calcula g(x). Luego ese resultado se pasa a f.
En f(g(x)), la función que está más adentro se ejecuta primero.
Este orden es importante porque cambiarlo puede producir un resultado diferente.
Definamos dos funciones simples: una suma 3 y otra duplica.
function sumarTres(x) {
return x + 3;
}
function duplicar(x) {
return x * 2;
}
console.log(sumarTres(5));
console.log(duplicar(5));
Podemos usar la salida de una función como entrada de la otra.
function sumarTres(x) {
return x + 3;
}
function duplicar(x) {
return x * 2;
}
const resultado = duplicar(sumarTres(5));
console.log(resultado);
Primero se calcula sumarTres(5), que da 8. Luego se calcula duplicar(8), que da 16.
Componer las mismas funciones en otro orden puede cambiar el resultado.
function sumarTres(x) {
return x + 3;
}
function duplicar(x) {
return x * 2;
}
const primeroSumar = duplicar(sumarTres(5));
const primeroDuplicar = sumarTres(duplicar(5));
console.log(primeroSumar);
console.log(primeroDuplicar);
| Notación matemática | Lectura | En JavaScript |
|---|---|---|
| f(g(x)) | Primero g, luego f | f(g(x)) |
| g(f(x)) | Primero f, luego g | g(f(x)) |
| (f ∘ g)(x) | f compuesta con g | compose(f, g)(x) |
Podemos crear una función que reciba dos funciones y devuelva una nueva función compuesta.
function compose(f, g) {
return function(x) {
return f(g(x));
};
}
function sumarTres(x) {
return x + 3;
}
function duplicar(x) {
return x * 2;
}
const duplicarDespuesDeSumar = compose(duplicar, sumarTres);
console.log(duplicarDespuesDeSumar(5));
También es posible encadenar varias funciones. Cada salida alimenta la siguiente entrada.
function limpiarTexto(texto) {
return texto.trim();
}
function pasarAMinusculas(texto) {
return texto.toLowerCase();
}
function reemplazarEspacios(texto) {
return texto.replaceAll(" ", "-");
}
const resultado = reemplazarEspacios(pasarAMinusculas(limpiarTexto(" Hola Mundo JS ")));
console.log(resultado);
En programación, muchas veces se prefiere leer las transformaciones de izquierda a derecha. A este estilo se lo suele llamar canalización o pipeline.
function pipe(valor, funciones) {
let resultado = valor;
for (const funcion of funciones) {
resultado = funcion(resultado);
}
return resultado;
}
function duplicar(x) {
return x * 2;
}
function sumarTres(x) {
return x + 3;
}
console.log(pipe(5, [duplicar, sumarTres]));
Un flujo de datos puede verse como una composición de funciones pequeñas.
function limitar(valor) {
return Math.max(0, Math.min(100, valor));
}
function normalizar(valor) {
return valor / 100;
}
function porcentaje(valor) {
return Math.round(valor * 100) + "%";
}
const valor = porcentaje(normalizar(limitar(135)));
console.log(valor);
Al graficar, se suele convertir un valor matemático a un valor normalizado y luego a coordenadas de pantalla.
function normalizar(valor) {
return (valor + 10) / 20;
}
function aPixeles(valorNormalizado) {
return valorNormalizado * 400;
}
function valorAPantalla(valor) {
return aPixeles(normalizar(valor));
}
console.log(valorAPantalla(-10));
console.log(valorAPantalla(0));
console.log(valorAPantalla(10));
Las transformaciones vistas en temas anteriores también pueden componerse.
En esa expresión se combinan traslación horizontal, escalado vertical y traslación vertical.
Este ejemplo aplica varias transformaciones a una función base.
function cuadratica(x) {
return x * x;
}
function transformada(x) {
return 2 * cuadratica(x - 3) + 5;
}
console.log(transformada(3));
console.log(transformada(4));
console.log(transformada(5));
Para que f(g(x)) tenga sentido, el resultado de g(x) debe pertenecer al dominio de f.
En programación, esto equivale a asegurarse de que cada paso entregue datos válidos para el paso siguiente.
Si una función espera valores positivos, conviene controlar la entrada antes de continuar.
function raiz(valor) {
if (valor < 0) {
return "Entrada inválida";
}
return Math.sqrt(valor);
}
function restarDiez(x) {
return x - 10;
}
console.log(raiz(restarDiez(25)));
console.log(raiz(restarDiez(4)));
Los métodos como map, filter y reduce suelen usarse como pasos de una composición de datos.
const numeros = [1, 2, 3, 4, 5, 6];
const resultado = numeros
.filter(function(numero) {
return numero % 2 === 0;
})
.map(function(numero) {
return numero * numero;
});
console.log(resultado);
La composición de funciones permite construir operaciones complejas a partir de pasos simples. Es una idea matemática muy cercana a la programación: cada función resuelve una parte del problema y el encadenamiento define el comportamiento completo.