Volver al inicio

HKT en TypeScript: emulación y práctica

El artículo cubre la emulación de Tipos de orden superior en TypeScript a través de mapeos de Kind y bibliotecas. Describe limitaciones de aridad, implementaciones de map para contenedores y barreras para el soporte nativo. Útil para crear abstracciones FP seguras en tipos.

Emulación de HKT en TypeScript: Kind y bibliotecas
Advertisement 728x90

Emular tipos de orden superior en TypeScript: Enfoques prácticos

TypeScript no soporta de forma nativa los tipos de orden superior (HKT), lo que limita la creación de abstracciones reutilizables sobre constructores de tipo como Array, Set o Promise. Los HKT permiten parametrizar no solo los tipos de elementos, sino también el contenedor en sí, facilitando una tipificación fuerte para patrones funcionales como funtores y mónadas. Sin ellos, terminas duplicando código para operaciones similares a map en cada tipo de dato.

Imagina una tarea típica: implementar map para Set.

type MapSet = <A, B>(f: (a: A) => B, set: Set<A>) => Set<B>;

De forma similar, necesitarías versiones para ReadonlySet, LinkedList, ReadonlyArray. La duplicación es inevitable sin HKT, donde una interfaz podría ser simplemente Mappable<F> con una firma F<A> → F<B>.

Google AdInline article slot

TypeScript arroja un error: El tipo ‘F’ no es genérico, porque no puede distinguir entre tipos de orden ( para string y → * para Array).

Emulación mediante diccionario de constructores

La solución más sencilla es un diccionario que mapea claves de cadena a tipos.

interface TypeConstructors<A> {
  'Array': Array<A>;
  'LinkedList': LinkedList<A>;
  'ReadonlySet': ReadonlySet<A>;
  'Set': Set<A>;
}

type Kind<F extends keyof TypeConstructors<unknown>, A> = TypeConstructors<A>[F];

Kind<'Array', number> se resuelve como Array<number>. Sobre esta base construimos un tipo genérico:

Google AdInline article slot
type Mappable<F extends keyof TypeConstructors<unknown>> = <A, B>(
  f: (a: A) => B,
  list: Kind<F, A>
) => Kind<F, B>;

Implementaciones para tipos específicos:

const mapArray: Mappable<'Array'> = (f, list) => list.map(f);
const mapSet: Mappable<'Set'> = (f, list) => new Set(Array.from(list, f));

Limitaciones de aridad y soluciones

Este enfoque funciona bien para constructores unarios (). Para binarios como Map<K, V>, necesitas un diccionario separado:

interface TypeConstructors2<A, B> {
  Map: Map<A, B>;
  Record: Record<A, B>;
}

type Kind2<F extends keyof TypeConstructors2<unknown, unknown>, A, B> = TypeConstructors2<A, B>[F];

Esto complica el manejo de tipos de múltiples aridades. En proyectos de producción, los desarrolladores dependen de bibliotecas:

Google AdInline article slot
  • hkt-toolbelt: Soporta hasta 4 parámetros, incluye utilidades para funtores y mónadas.
  • hkt-core: Se enfoca en rendimiento de tipos, superficie de API mínima.

Estas herramientas ofrecen inferencia de tipos cercana a la nativa, eliminando la necesidad de gestionar manualmente diccionarios.

Principales desafíos en adoptar HKT

A pesar de las constantes solicitudes (issue #1213, #55280), los HKT siguen ausentes en 2026. Las principales barreras son:

  • Complejidad del compilador: Introducir polimorfismo de orden, extender la inferencia de tipos y adaptarse al tipado estructural.
  • Rendimiento: Tiempos de compilación aumentados en proyectos grandes de programación funcional.
  • Compromisos de diseño: Equilibrar expresividad con compatibilidad con JavaScript y evitar sobrecarga sintáctica.

Se han propuesto soluciones como:

  • Un marcador especial como F<~> para constructores de tipo.
  • Mejorar los tipos condicionales sin añadir nueva sintaxis.
  • Emulación pragmática usando características existentes de TypeScript.

Lo que realmente importa

  • Los HKT se emulan mediante mapeos de Kind, preservando la tipificación estricta de contenedores.
  • Bibliotecas como hkt-toolbelt y hkt-core simplifican la integración en proyectos intermedios y avanzados.
  • La aridad requiere diccionarios separados; los tipos unarios son ideales para casos fundamentales.
  • El soporte nativo futuro podría llegar mediante mejoras incrementales que no comprometan el rendimiento.
  • La emulación permite implementaciones limpias de map, flatMap y funciones similares sin duplicar código entre Array, Set, Promise y más.

— Editorial Team

Advertisement 728x90

Leer después