UNAH · Tegucigalpa / Cátedra de Programación III Período 2026
Contenido Oficial · Módulos 01, 02 y 03 Activos
MÓDULO 03 Paradigma Funcional

3. El Paradigma Funcional

En el Módulo 02 vimos que el paradigma declarativo se enfoca en el «QUÉ» y no en el «CÓMO». La programación funcional es la rama declarativa que construye programas combinando funciones, igual que en matemáticas: los datos entran a una función, salen transformados y nada más se altera en el camino.

🎯 Objetivos del Módulo 03
  • Explicar la idea central del paradigma funcional y de dónde proviene.
  • Identificar sus características: funciones puras, inmutabilidad, funciones de primera clase, orden superior, composición y recursión.
  • Aplicar map, filter y reduce para resolver problemas sin bucles manuales.
  • Evaluar las ventajas y desventajas del paradigma y reconocer dónde se usa hoy en la industria.
💻 Los bloques de código de este módulo están ocultos. Ábrelos uno por uno o todos a la vez.
ANALOGÍA

🏭 Una línea de producción

Cada estación recibe algo, lo transforma y lo entrega a la siguiente: fruta → lavar → pelar → licuar → jugo. Ninguna estación modifica lo que ocurre en otra, y con la misma fruta siempre se obtiene el mismo jugo. Cada estación es una función.

MATEMÁTICA

📐 Función matemática

Si f(x) = x², entonces f(3) vale 9 hoy, mañana y siempre. No depende de la hora, ni de otras variables, ni «cambia» a x. Esa es exactamente la clase de función que busca este paradigma.

Primer contacto: el mismo problema, dos mentalidades

💡 ¿Por qué usamos JavaScript en este módulo?

Existen lenguajes funcionales «puros» como Haskell, pero JavaScript es multiparadigma: permite escribir tanto de forma imperativa como funcional. Eso nos deja comparar ambos estilos en un lenguaje conocido y aplicar lo aprendido desde hoy en cualquier proyecto web.

🕰️

Un Poco de Historia: del Cálculo Lambda a JavaScript

De la teoría matemática (1936) a los lenguajes que usamos hoy
λ Línea del tiempo del paradigma funcional
1936 · Alonzo Church

El Cálculo Lambda (λ-cálculo)

Un sistema matemático donde todo se expresa con funciones: definirlas y aplicarlas. Se demostró que es tan poderoso como la Máquina de Turing (Tesis de Church-Turing). Es la base teórica de todo el paradigma.

1958 · John McCarthy (MIT)

LISP: el primer lenguaje funcional

Llevó el cálculo lambda a una computadora real y fue el lenguaje de la investigación en Inteligencia Artificial por décadas. Aportó ideas que hoy damos por sentadas, como la recolección automática de basura (garbage collector) y el tipado dinámico.

1973 – 1990 · ML, Erlang y Haskell

Maduración académica e industrial

ML (1973) introdujo la inferencia de tipos; Erlang (1986, Ericsson) llevó lo funcional a las telecomunicaciones; Haskell (1990) se convirtió en el estándar de lenguaje funcional puro.

2004 – hoy · La ola multiparadigma

Lo funcional llega a los lenguajes populares

Scala (2004), Clojure (2007), F# (2005) y Elixir (2012) nacen funcionales. Los lenguajes masivos adoptan sus ideas: Java 8 (2014) incorpora lambdas y Streams; JavaScript ES6 (2015) agrega funciones flecha; Python, C# y Kotlin hacen lo mismo.

La herencia es directa: la notación λx. x * 2 de Church es, literalmente, lo que hoy escribimos en JavaScript como función flecha.

🧪

Pilar 1 · Funciones Puras

El corazón del paradigma: mismas entradas, misma salida, sin efectos secundarios

Una función es pura cuando cumple dos reglas:

REGLA 1

Determinista

Con los mismos argumentos siempre devuelve el mismo resultado. Su salida depende únicamente de sus parámetros.

REGLA 2

Sin efectos secundarios

No modifica nada fuera de ella: ni variables globales, ni los objetos que recibe, ni archivos, ni la pantalla. Solo calcula y devuelve.

⚠️ Efectos secundarios más comunes

Modificar una variable global · cambiar un objeto o arreglo recibido por parámetro · imprimir en consola o en pantalla · leer o escribir archivos y bases de datos · hacer peticiones a internet · usar Math.random() o new Date().

Ojo: un programa útil necesita efectos (mostrar resultados, guardar datos). La programación funcional no los elimina: los aísla en pocos lugares bien identificados y mantiene puro todo lo demás.

Transparencia referencial

Como una función pura siempre da el mismo resultado, podemos reemplazar la llamada por su valor sin cambiar el comportamiento del programa. Esto hace el código fácil de razonar, de probar y de optimizar (por ejemplo, guardando resultados en caché).

🧊

Pilar 2 · Inmutabilidad

Los datos no se modifican: se crean versiones nuevas

En el paradigma funcional, una vez creado un dato no se cambia. Si necesitamos una versión distinta, construimos una copia nueva con el cambio. Veamos por qué importa con un error muy común:

Lo mismo aplica a los objetos: en lugar de modificar una propiedad, se copia el objeto con el operador spread (...).

Operación❌ Muta el original✅ Alternativa inmutable
Agregar al finalarr.push(x)[...arr, x]
Eliminar elementosarr.splice(i, 1)arr.filter(...)
Ordenararr.sort()arr.toSorted() o [...arr].sort()
Invertirarr.reverse()arr.toReversed()
Cambiar una propiedadobj.nota = 85{ ...obj, nota: 85 }
⚠️ Trampa frecuente: const no significa inmutable

const solo impide reasignar la variable, pero el contenido de un arreglo u objeto sí puede cambiar.

✔ ¿Qué ganamos con la inmutabilidad?
  • Menos errores sorpresa: nadie puede cambiar «a escondidas» un dato que otra parte del programa está usando.
  • Historial gratuito: como las versiones anteriores no se destruyen, es fácil implementar «deshacer» o comparar estados.
  • Concurrencia segura: varios hilos o procesadores pueden leer los mismos datos sin bloqueos, porque nadie los modifica.
🎖️

Pilar 3 · Funciones de Primera Clase y de Orden Superior

Las funciones son valores, igual que un número o un texto

Antes de continuar, un repaso rápido de la sintaxis de funciones flecha (arrow functions), que usaremos en el resto del módulo:

PRIMERA CLASE

Funciones como ciudadanos de primera clase

Una función puede hacer todo lo que hace cualquier otro valor: guardarse en una variable, pasarse como argumento, devolverse como resultado o almacenarse en un arreglo u objeto.

ORDEN SUPERIOR

Funciones de orden superior

Son funciones que reciben otra función como parámetro o que devuelven una función. map, filter y reduce son los ejemplos más famosos.

💡 Closure (clausura)

En crearMultiplicador, la función devuelta «recuerda» el valor de factor aunque crearMultiplicador ya terminó de ejecutarse. A esa memoria se le llama closure y permite fabricar funciones especializadas a partir de una general.

🧰

Las Tres Herramientas Esenciales: map, filter y reduce

Recorrer colecciones sin escribir un solo bucle

Estas tres funciones de orden superior reemplazan la gran mayoría de los ciclos for. Trabajaremos con estos datos en todos los ejemplos:

TRANSFORMAR

map

Aplica una función a cada elemento y devuelve un arreglo nuevo del mismo tamaño.

[🍎, 🍎, 🍎] → [🍏, 🍏, 🍏]

SELECCIONAR

filter

Conserva solo los elementos que cumplen una condición (la función devuelve true o false).

[🍎, 🍌, 🍎] → [🍎, 🍎]

RESUMIR

reduce

Combina todos los elementos, uno a uno, hasta obtener un solo valor: una suma, un máximo, un objeto…

[1, 2, 3] → 6

Cara a cara: promedio de los estudiantes aprobados

La versión funcional se lee casi como la especificación del problema: «de los estudiantes, filtra los aprobados, toma sus notas y promédialas». No hay contadores, índices ni variables que cambien de valor.

📚 Otras funciones de orden superior útiles

find (primer elemento que cumple) · some (¿alguno cumple?) · every (¿todos cumplen?) · forEach (recorre, pero solo para efectos como imprimir; no devuelve nada).

🔗

Composición de Funciones

Construir soluciones grandes uniendo funciones pequeñas

En matemáticas, componer es aplicar una función al resultado de otra: (f ∘ g)(x) = f(g(x)). En programación funcional, se resuelven problemas complejos armando una tubería (pipeline) de funciones simples, como las estaciones de la línea de producción.

Forma 1: encadenamiento de métodos

Forma 2: composición con pipe

Cuando las funciones no son métodos de un arreglo, podemos crear una utilidad pipe que las ejecute en orden, pasando el resultado de una a la siguiente:

🧩 La idea clave

" Paradigmas de Programación " → quitarEspacios → aMinusculas → cambiarEspacios → "paradigmas-de-programación"

Cada pieza es pequeña, pura y se puede probar por separado. Para cambiar el comportamiento basta con agregar, quitar o reordenar piezas.

🔄

Recursión: Repetir sin Bucles

Una función que se llama a sí misma con un problema más pequeño

Los bucles for y while dependen de un contador que cambia (i++), lo cual choca con la inmutabilidad. Los lenguajes funcionales puros ni siquiera tienen bucles: repiten mediante recursión. Toda función recursiva tiene dos partes:

PARTE 1

🛑 Caso base

La condición en la que la función deja de llamarse y devuelve un resultado directo. Sin él, la recursión nunca termina.

PARTE 2

🔁 Paso recursivo

La función se llama a sí misma con un problema más pequeño, acercándose cada vez al caso base.

⚠️ Recursión en JavaScript: úsala con criterio

Cada llamada ocupa espacio en la pila de llamadas (call stack). Con listas muy grandes (decenas de miles de elementos) JavaScript lanza el error Maximum call stack size exceeded. Por eso, en JavaScript lo habitual es usar map, filter y reduce para recorrer colecciones, y reservar la recursión para problemas naturalmente recursivos: árboles, carpetas, menús anidados o JSON con niveles.

⚖️

Ventajas y Desventajas del Paradigma Funcional

Ningún paradigma es perfecto: se trata de elegir la herramienta adecuada
✔ Ventajas
  • Código predecible: las funciones puras siempre responden igual, por lo que hay menos errores «misteriosos».
  • Fácil de probar: para testear una función pura basta con darle entradas y revisar la salida; no hay que preparar estado global.
  • Reutilizable y modular: funciones pequeñas que se combinan como piezas de LEGO.
  • Concurrencia y paralelismo más seguros: sin datos compartidos que cambien, no hay condiciones de carrera.
  • Código más expresivo y corto: se describe qué se quiere, no cada paso.
✖ Desventajas
  • Curva de aprendizaje: exige cambiar la forma de pensar si venimos del estilo imperativo.
  • Consumo de memoria: crear copias en lugar de modificar puede costar más memoria y tiempo.
  • Recursión profunda: en lenguajes como JavaScript puede desbordar la pila.
  • El mundo real tiene estado: bases de datos, interfaces y archivos obligan a manejar efectos con cuidado.
  • Abuso de la abstracción: cadenas de funciones muy largas o «demasiado ingeniosas» pueden volverse difíciles de leer.
CriterioImperativoFuncional
Unidad principalInstrucciones que se ejecutan en ordenFunciones que transforman datos
DatosMutables (x = x + 1)Inmutables (se crean copias)
Repeticiónfor, whilemap, filter, reduce, recursión
EstadoCompartido y cambianteSe evita o se aísla
Ideal paraControl fino del hardware, algoritmos de bajo nivel, rendimiento extremoTransformación de datos, lógica de negocio, sistemas concurrentes
🌎

El Paradigma Funcional en el Mundo Real

Lenguajes, niveles de «pureza» y dónde lo encontrarán como profesionales
FUNCIONAL PURO

Haskell

No permite variables mutables ni efectos secundarios sin declararlos. Muy usado en investigación, compiladores y finanzas.

FUNCIONAL PRIMERO

Clojure, Elixir, Erlang, F#, Scala

Promueven funciones puras e inmutabilidad por defecto, pero permiten efectos cuando hace falta.

MULTIPARADIGMA

JavaScript, Python, Java, C#, Kotlin

Nacieron imperativos u orientados a objetos, pero hoy incorporan lambdas, map/filter/reduce y otras herramientas funcionales.

¿Dónde lo usarán?

  • Desarrollo web con React: los componentes son funciones que reciben datos y devuelven la interfaz, y el estado se actualiza de forma inmutable.
  • Big Data: el modelo MapReduce de Google y herramientas como Apache Spark se basan en las mismas ideas de map y reduce para procesar datos en miles de servidores en paralelo.
  • Sistemas de alta disponibilidad: WhatsApp construyó su servidor de mensajería sobre Erlang, aprovechando la concurrencia sin estado compartido.
  • Java moderno: la Stream API que vimos en el Módulo 02 es programación funcional dentro de Java.
🔭 Conceptos avanzados que escucharán más adelante

Currificación (currying): transformar una función de varios parámetros en una cadena de funciones de un solo parámetro.

Evaluación perezosa (lazy evaluation): una expresión no se calcula hasta que su valor realmente se necesita; Haskell la usa por defecto y permite trabajar con listas «infinitas».

Mónadas: el mecanismo que usan los lenguajes puros, como Haskell, para manejar efectos (leer teclado, escribir archivos) sin romper la pureza.

🛠️

Taller Práctico y Autoevaluación

Practica los conceptos del módulo · soluciones incluidas
✍️ Ejercicio 1 · ¿Pura o impura?
Ver solución
A) Pura: solo depende de r y no modifica nada.
B) Impura: modifica la variable externa contador y su resultado cambia en cada llamada.
C) Impura: imprimir en consola es un efecto secundario (además no devuelve nada).
D) Pura: solo lee la propiedad recibida y devuelve true o false.
✍️ Ejercicio 2 · De imperativo a funcional
Ver solución
✅ solucion_2.js
const totalInventario = productos
  .filter(p => p.stock > 0)                 // solo los que tienen existencias
  .map(p => p.precio * p.stock)             // valor de cada producto
  .reduce((acc, valor) => acc + valor, 0);  // sumar todo

console.log(totalInventario); // 7750
✍️ Ejercicio 3 · Piensa en recursión
Ver pista
¿Cuál es la lista más pequeña posible cuyo máximo conoces sin calcular nada? Ese es tu caso base. Después compara el primer elemento con el máximo del resto.
Ver solución
✅ solucion_3.js
function maximo(lista) {
  if (lista.length === 1) return lista[0];   // caso base
  const [primero, ...resto] = lista;
  const maxResto = maximo(resto);            // resolver el problema más pequeño
  return primero > maxResto ? primero : maxResto;
}

console.log(maximo([3, 9, 2, 7])); // 9
📝 Autoevaluación del Módulo 03
1. ¿Qué característica define a una función pura? Conceptos
A) Que está escrita con la sintaxis de función flecha.
B) Que con las mismas entradas siempre devuelve el mismo resultado y no produce efectos secundarios.
C) Que no recibe parámetros.
Ver explicación
La sintaxis no importa: una función tradicional también puede ser pura. Lo que la define es ser determinista y no tener efectos secundarios.
2. Si const a = [1, 2], ¿qué instrucción respeta la inmutabilidad? Inmutabilidad
A) a.push(3)
B) a[0] = 10
C) const b = [...a, 3]
Ver explicación
push y la asignación por índice modifican el arreglo original. El operador spread crea un arreglo nuevo y deja a intacto.
3. ¿Qué devuelve [3, 8, 5].filter(n => n > 4)? Orden superior
A) [8, 5]
B) [true, false, true]
C) 13
Ver explicación
filter conserva los elementos para los que la función devuelve true. Devolver booleanos para cada elemento sería lo que hace map con esa misma función.
4. ¿Qué ocurre si una función recursiva no tiene caso base? Recursión
A) Devuelve undefined automáticamente.
B) Se llama a sí misma indefinidamente hasta desbordar la pila de llamadas.
C) JavaScript la convierte en un bucle for.
Ver explicación
Sin caso base, la función nunca deja de llamarse. En JavaScript esto termina con el error Maximum call stack size exceeded.
5. ¿Cuál es una desventaja real del paradigma funcional? Evaluación crítica
A) No permite reutilizar código.
B) Hace imposible la programación concurrente.
C) Crear copias en lugar de modificar datos puede consumir más memoria.
Ver explicación
Al contrario de las otras opciones, lo funcional favorece la reutilización y la concurrencia. Su costo principal es de memoria y rendimiento por las copias, además de la curva de aprendizaje.
🧭 Resumen del Módulo 03

Programar funcionalmente es transformar datos con funciones puras, sin modificar lo existente. Las funciones son valores que se pasan y se combinan; map, filter y reduce reemplazan a los bucles; y la recursión resuelve problemas dividiéndolos en versiones más pequeñas. El resultado es código más predecible y fácil de probar, a cambio de una nueva forma de pensar y cierto costo de memoria.