concepto · intermedio · 14 min de lectura
Programación Funcional
La programación funcional organiza el código alrededor de funciones puras y datos inmutables, transformando información mediante composición en vez de mutar estado paso a paso.
No requiere conocimientos previos.
Alcance en breve
Cubre
- functional programming as a paradigm centered on pure functions and immutability
- first-class functions, higher-order functions, and closures
- declarative data transformation: map, filter, reduce
- function composition as the primary mechanism for building behavior
- contrast with object-oriented and procedural paradigms
No cubre
- advanced type theory, monads, and category theory
- lazy evaluation and thunks beyond conceptual mention
- specific functional languages (Haskell, Elm, Clojure) beyond brief reference
Supone
- The reader understands basic programming concepts: variables, functions, loops, and conditionals.
- The reader has written or read imperative code and is curious about alternatives.
Resumen
La programación funcional (FP, por Functional Programming) es un paradigma que trata la computación como la evaluación de funciones matemáticas y evita el cambio de estado y los datos mutables. En vez de decirle al programa cómo hacer algo paso a paso (estilo imperativo), la FP describe qué resultado se quiere obtener transformando datos mediante funciones.
Importa porque produce código más predecible y más fácil de testear. Una función pura —que para los mismos argumentos devuelve siempre el mismo resultado y no modifica nada fuera de su alcance— se puede entender de forma aislada, sin leer el resto del sistema. Cuando todas las funciones son así, el programa se vuelve una cadena de transformaciones donde cada eslabón es verificable por separado.
La FP no es un reemplazo total de la programación orientada a objetos ni de la procedural. Es un conjunto de principios que se pueden aplicar donde tengan sentido. De hecho, los lenguajes modernos mezclan los tres paradigmas: TypeScript, Python, Rust y Kotlin permiten escribir tanto clases como funciones puras y pipelines de transformación. La FP te da una caja de herramientas; vos decidís cuándo usarlas.
Alcance y supuestos
Este paquete cubre los principios centrales de la programación funcional: funciones puras, inmutabilidad, funciones de primera clase y de orden superior, transformación declarativa con map/filter/reduce, y composición de funciones. También contrasta el estilo funcional con el imperativo y el orientado a objetos.
No cubre teoría de categorías, mónadas, functores, evaluación perezosa ni tipos algebraicos avanzados. Tampoco cubre lenguajes puramente funcionales como Haskell o Elm más allá de una referencia breve.
Asume que entendés estructuras de control básicas (bucles, condicionales) y que has trabajado con arrays y objetos. Los ejemplos usan TypeScript moderno.
Modelo mental
Pensá en una línea de montaje en una fábrica. Una pieza entra por un extremo, pasa por una estación que la pinta, después por otra que le agrega un componente, después por otra que la empaqueta. Cada estación recibe una pieza, hace su trabajo y entrega una pieza nueva. La original no se modifica: cada estación produce una versión transformada.
Esa es la programación funcional. Tus datos son las piezas. Tus funciones son las estaciones. Cada función recibe datos, los transforma y devuelve datos nuevos. Los datos originales permanecen intactos. No hay una estación que raye la pieza original y después otra que intente arreglarla: cada transformación es independiente y no deja rastros laterales.
Compará eso con el estilo imperativo: alguien con un marcador va tachando y reescribiendo sobre la misma hoja. Si en el paso 3 escribió mal, el paso 7 hereda el error y es difícil saber en qué momento se introdujo. La FP evita ese problema porque cada paso produce una hoja nueva a partir de la anterior, y la anterior sigue existiendo para comparar.
Uso práctico
Aplicá principios funcionales cuando la lógica se entiende mejor como transformación de datos que como secuencia de instrucciones:
- ✅ Transformar una lista de objetos en otra forma (filtrar, ordenar, proyectar campos).
- ✅ Procesar datos que llegan por una API y necesitan varias etapas de limpieza y enriquecimiento.
- ✅ Operaciones donde el orden de ejecución no debería afectar el resultado (independencia de efectos laterales).
- ❌ Manejar estado compartido que muchas partes del sistema necesitan modificar sincronizadamente: ahí un enfoque con objetos o actores puede ser más claro.
- ❌ Scripts cortos de 20 líneas donde introducir
mapyfilteroscurece lo que unforsimple resuelve.
Los principios en acción
Funciones puras
Una función es pura cuando cumple dos reglas: dado el mismo input, devuelve siempre el mismo output; y no produce efectos laterales observables (no modifica variables externas, no escribe en disco, no llama a una API).
// Impura: depende de una variable externa
let tax = 0.21;
function addTax(amount: number) {
return amount + amount * tax;
}
// Pura: todo lo que necesita está en los argumentos
function addTax(amount: number, tax: number) {
return amount + amount * tax;
}
Las funciones puras son las más fáciles de testear, razonar y ejecutar en paralelo. No necesitás configurar un estado global antes del test ni limpiarlo después.
Inmutabilidad
En FP, los datos no se modifican: se crean versiones nuevas con los cambios aplicados.
// Mutación: modifica el objeto original
const user = { name: "Ana", score: 10 };
user.score += 5;
// Inmutabilidad: crea un objeto nuevo
const user = { name: "Ana", score: 10 };
const updated = { ...user, score: user.score + 5 };
La inmutabilidad elimina una fuente entera de bugs: el código que recibe un dato no puede romper el código que también lo usa, porque ninguno modifica el original. En TypeScript, Object.freeze, readonly en tipos y el operador spread son herramientas que refuerzan este principio.
Funciones de primera clase y orden superior
En FP, las funciones son valores: podés pasarlas como argumentos, devolverlas desde otras funciones y asignarlas a variables. Una función que recibe o devuelve otra función es de orden superior.
// Función de orden superior: recibe una función y la aplica a cada elemento
function map<T, U>(arr: T[], fn: (item: T) => U): U[] {
const result: U[] = [];
for (const item of arr) {
result.push(fn(item));
}
return result;
}
const doubled = map([1, 2, 3], x => x * 2); // [2, 4, 6]
JavaScript y TypeScript tratan las funciones como ciudadanos de primera clase desde su diseño. Eso permite patrones como callbacks, promesas encadenadas y decoradores sin esfuerzo extra.
Transformación declarativa: map, filter, reduce
El trío central de la FP aplicada a colecciones. En vez de decirle al programa cómo iterar, le decís qué querés obtener.
const orders = [
{ id: 1, total: 120, status: "delivered" },
{ id: 2, total: 45, status: "pending" },
{ id: 3, total: 200, status: "delivered" },
];
// Enfoque imperativo: cómo hacerlo
const deliveredTotalsImperative: number[] = [];
for (const order of orders) {
if (order.status === "delivered") {
deliveredTotalsImperative.push(order.total);
}
}
// Enfoque funcional: qué resultado quiero
const deliveredTotals = orders
.filter(order => order.status === "delivered")
.map(order => order.total);
// [120, 200]
Cada método devuelve un array nuevo. Podés leer el pipeline de izquierda a derecha como una frase: «filtrá las órdenes entregadas, después extraé sus totales». No hay índices, no hay acumuladores manuales, no hay posibilidad de modificar el array original sin querer.
// reduce: consolida un array en un solo valor
const totalRevenue = orders
.filter(order => order.status === "delivered")
.map(order => order.total)
.reduce((sum, total) => sum + total, 0);
// 320
Composición de funciones
La composición es el mecanismo principal para construir comportamiento en FP: combinás funciones pequeñas para crear funciones más grandes.
const addTax = (rate: number) => (amount: number) => amount + amount * rate;
const formatCurrency = (amount: number) => `$${amount.toFixed(2)}`;
const priceWithTax = addTax(0.21)(100); // 121
const formatted = formatCurrency(priceWithTax); // "$121.00"
// Composición: una función que aplica ambas en secuencia
const compose = <A, B, C>(f: (b: B) => C, g: (a: A) => B) => (a: A) => f(g(a));
const priceTag = compose(formatCurrency, addTax(0.21));
priceTag(100); // "$121.00"
priceTag es una función nueva construida a partir de dos existentes. No modificaste addTax ni formatCurrency: las combinaste. Ese es el superpoder de la composición: construís comportamiento complejo desde piezas simples, testeadas y reutilizables.
Contraste con imperativo y POO
| Aspecto | Funcional | Imperativo | POO | |---|---|---|---| | Enfoque | Qué resultado | Cómo obtenerlo | Quién es responsable | | Estado | Inmutable, transformado | Mutable, modificado in-place | Encapsulado en objetos | | Unidad base | Función | Instrucción | Objeto (datos + métodos) | | Flujo de datos | Pipelines explícitos | Variables que cambian | Mensajes entre objetos | | Efectos laterales | Minimizados, explícitos | Libres, a veces implícitos | Confinados al objeto | | Reutilización | Composición de funciones | Procedimientos compartidos | Herencia y composición |
Ninguno es universalmente superior. La FP brilla en transformación de datos, procesamiento de streams y lógica sin estado. La POO brilla modelando entidades con identidad y reglas de negocio. El estilo imperativo brilla en scripts simples y automatización. Un buen sistema usa los tres donde cada uno aporta claridad.
Ejemplo trabajado: refactorizar un reporte de ventas
Un sistema genera un reporte de ventas a partir de un array de transacciones. La versión original es imperativa:
interface Transaction {
product: string;
category: string;
amount: number;
}
function buildReport(transactions: Transaction[]) {
const report: Record<string, number> = {};
for (const tx of transactions) {
if (tx.category === "electronics") {
if (report[tx.product] === undefined) {
report[tx.product] = 0;
}
report[tx.product] += tx.amount;
}
}
return report;
}
El código funciona, pero mezcla tres responsabilidades: filtrar por categoría, agrupar por producto y sumar montos. Si mañana necesitás agregar un filtro por fecha o cambiar la categoría, tenés que reescribir el bucle.
La misma lógica en estilo funcional separa cada paso:
function buildReport(transactions: Transaction[]) {
return transactions
.filter(tx => tx.category === "electronics")
.reduce((report, tx) => {
const current = report.get(tx.product) ?? 0;
report.set(tx.product, current + tx.amount);
return report;
}, new Map<string, number>());
}
Pero se puede ir más lejos y extraer cada etapa como una función con nombre, componiéndolas en un pipeline explícito:
const byCategory = (category: string) =>
(txs: Transaction[]) => txs.filter(tx => tx.category === category);
const sumByProduct = (txs: Transaction[]) =>
txs.reduce((map, tx) => {
map.set(tx.product, (map.get(tx.product) ?? 0) + tx.amount);
return map;
}, new Map<string, number>());
const electronicsReport = (txs: Transaction[]) => sumByProduct(byCategory("electronics")(txs));
Cada función hace una sola cosa y se puede testear por separado. Si el producto cambia y ahora pide "books" en vez de "electronics", cambiás un string, no una condición dentro de un bucle. Si querés agregar un paso de normalización de precios antes de sumar, insertás una función nueva en la cadena sin tocar las existentes.
Para cerrar, tres preguntas antes de aplicar FP:
- ¿El problema se entiende mejor como una secuencia de transformaciones de datos?
- ¿Las operaciones pueden ser independientes entre sí, sin compartir estado mutable?
- ¿La claridad que ganás al separar cada paso justifica la verbosidad adicional frente a un solo bucle?
Si las respuestas son sí, la FP va a hacer tu código más predecible, más testeable y más fácil de modificar. Si el problema es un script de 15 líneas con dos condiciones, un for simple también está bien.
Evidencia
- Wikipedia, Functional programming presenta la historia, los conceptos fundamentales y los lenguajes funcionales puros e híbridos.
- Professor Frisby's Mostly Adequate Guide to Functional Programming es una introducción práctica a FP en JavaScript que cubre funciones puras, currying, composición y mónadas con ejemplos ejecutables.
- MDN Web Docs, Functions explica funciones de primera clase, expresiones lambda y closures en JavaScript, los bloques sobre los que se construye la FP en TypeScript.
- Microsoft Learn, F# coding conventions muestra cómo un lenguaje funcional-first organiza código alrededor de composición e inmutabilidad.
Fuentes citadas
- Wikipedia, Functional programming (Síntesis, 21-07-2026)
- Professor Frisby's Mostly Adequate Guide to Functional Programming (Práctica, 21-07-2026)
- MDN Web Docs, Functions — reusable blocks of code (Oficial, 21-07-2026)
- Microsoft Learn, F# coding conventions (Oficial, 20-07-2026)