Programación funcional en TypeScript: nivel avanzado

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Programación funcional en TypeScript: nivel avanzado

La programación funcional (FP) es un paradigma que en los últimos años ha estado conquistando activamente el mundo de JavaScript y TypeScript. Para los desarrolladores que ya están familiarizados con los conceptos básicos (funciones puras, inmutabilidad de datos), se abre una nueva capa de posibilidades: trabajar con mónadas, functores, aplicativos y composiciones complejas. En este artículo, analizaremos conceptos avanzados de FP en TypeScript con ejemplos prácticos.



1. Inmutabilidad y uso compartido estructural de datos

La inmutabilidad es una piedra angular de la FP. En TypeScript podemos garantizar la inmutabilidad a nivel de tipos usando readonly y ReadonlyArray. Sin embargo, para trabajar eficientemente con estructuras de datos grandes (por ejemplo, Redux store) se utiliza el uso compartido estructural (structural sharing) — bibliotecas como Immer o Immutable.js.

Consideremos un ejemplo usando readonly y una función para actualizar un objeto anidado sin mutación:

type User = {  readonly id: number;  readonly name: string;  readonly address: {    readonly city: string;    readonly zip: string;  };};

const updateUserCity = (user: User, newCity: string): User => ({ ...user, address: { ...user.address, city: newCity, },});

const user: User = { id: 1, name: 'Alice', address: { city: 'NYC', zip: '10001' } };const updatedUser = updateUserCity(user, 'LA');console.log(user.address.city); // NYC (el original no cambió)console.log(updatedUser.address.city); // LA

Observa: creamos un nuevo objeto, pero reutilizamos las partes no modificadas (por ejemplo, id y name). Esto es uso compartido estructural manual. Para anidamiento profundo es mejor usar bibliotecas.



2. Functores y su implementación

Un functor es un contenedor que implementa el método map, permitiendo aplicar una función al contenido del contenedor sin violar su estructura. En TypeScript, un functor típico es Array. Pero podemos crear nuestro propio functor, por ejemplo, Maybe, para evitar errores con null y undefined.

Implementemos un functor Maybe simple:

class Maybe<T> {  private constructor(private value: T | null) {}

static of<U>(val: U | null | undefined): Maybe<U> { return new Maybe(val ?? null); }

map<U>(fn: (val: T) => U): Maybe<U> { if (this.value === null) { return Maybe.of<U>(null); } return Maybe.of(fn(this.value)); }

getOrElse(defaultValue: T): T { return this.value !== null ? this.value : defaultValue; }}

// Usoconst result = Maybe.of('hello') .map(s => s.toUpperCase()) .map(s => s + ' world') .getOrElse('fallback');console.log(result); // "HELLO WORLD"

const empty = Maybe.of<string>(null) .map(s => s.toUpperCase()) .getOrElse('default');console.log(empty); // "default"

El functor Maybe permite encadenar funciones de forma segura sin preocuparse por null. Cada map devuelve un nuevo Maybe, preservando el contexto.



3. Mónadas: cadenas de operaciones con efectos secundarios

Una mónada es un functor que también implementa el método flatMap (a menudo llamado bind o chain). Permite "desenvolver" contenedores anidados. Un ejemplo clásico de mónada es Promise o Array. Consideremos la mónada Either para el manejo de errores sin excepciones.

type Either<L, R> = Left<L> | Right<R>;

class Left<L> { readonly value: L; constructor(value: L) { this.value = value; } isLeft(): this is Left<L> { return true; } isRight(): this is Right<ne

Blogs

Book Recommendations