Zurück zur Startseite

HKT in TypeScript: Emulation und Praxis

Der Artikel behandelt die Emulation von Higher-Kinded Types in TypeScript durch Kind-Mappings und Bibliotheken. Beschreibt Arity-Beschränkungen, map-Implementierungen für Container und Barrieren zur nativen Unterstützung. Nützlich zum Erstellen typesicherer FP-Abstraktionen.

HKT-Emulation in TypeScript: Kind und Bibliotheken
Advertisement 728x90

Höherkodierte Typen in TypeScript emulieren: Praktische Ansätze

TypeScript unterstützt höhere kodierte Typen (HKT) nicht natively, was die Erstellung wiederverwendbarer Abstraktionen über Typkonstruktoren wie Array, Set oder Promise einschränkt. HKT ermöglichen es, nicht nur die Elementtypen, sondern auch den Container selbst zu parametrisieren und so starke Typisierung für funktionale Muster wie Funktoren und Monaden zu gewährleisten. Ohne sie müssen Sie Code für map-ähnliche Operationen bei jedem Datentyp duplizieren.

Betrachten wir eine typische Aufgabe: die Implementierung von map für Set.

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

Ebenso müssten Versionen für ReadonlySet, LinkedList, ReadonlyArray existieren. Ohne HKT ist Duplikation unvermeidlich – dort könnte eine Schnittstelle einfach Mappable<F> mit der Signatur F<A> → F<B> sein.

Google AdInline article slot

TypeScript wirft einen Fehler: Typ 'F' ist nicht generisch, da er zwischen Typarten ( für String und → * für Array) nicht unterscheiden kann.

Emulation mittels Konstruktor-Wörterbuch

Der einfachste Workaround ist ein Wörterbuch, das Zeichenketten-Schlüssel auf Typen abbildet.

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> ergibt Array<number>. Auf dieser Grundlage bauen wir einen generischen Typ auf:

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

Implementierungen für spezifische Typen:

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

Arity-Beschränkungen und Lösungen

Dieser Ansatz funktioniert gut für einstellige Konstruktoren (). Für zweistellige wie Map<K, V> benötigen Sie ein separates Wörterbuch:

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];

Das macht die Behandlung von Mehraritäts-Typen kompliziert. In Produktionsprojekten setzen Entwickler daher auf Bibliotheken:

Google AdInline article slot
  • hkt-toolbelt: Unterstützt bis zu 4 Parameter, enthält Hilfsmittel für Funktoren und Monaden.
  • hkt-core: Fokussiert auf Typleistung, minimalen API-Overhead.

Diese Tools liefern eine Typinferenz nahe an native HKT und entlasten vom manuellen Verwaltung von Wörterbüchern.

Hauptprobleme bei der Einführung von HKT

Trotz wiederholter Anfragen (Issue #1213, #55280) fehlen HKT im Jahr 2026 weiterhin. Haupthindernisse sind:

  • Compiler-Komplexität: Einführung von Art-Polymorphie, Erweiterung der Typinferenz, Anpassung an strukturelle Typisierung.
  • Leistung: Erhöhte Kompilierzeiten in großflächigen FP-Projekten.
  • Design-Entscheidungen: Ausbalancierung von Ausdruckskraft gegenüber JavaScript-Kompatibilität und Vermeidung von Syntax-Bloat.

Vorgeschlagene Lösungen beinhalten:

  • Ein spezieller Marker wie F<~> für Typkonstruktoren.
  • Verbesserung bedingter Typen ohne neue Syntax.
  • Pragmatische Emulation mithilfe bestehender TypeScript-Funktionen.

Was zählt wirklich

  • HKT werden mittels Kind-Zuordnungen emuliert und bewahren strikte Typisierung für Container.
  • Bibliotheken wie hkt-toolbelt und hkt-core vereinfachen die Integration in mittel- bis senior-level-Projekten.
  • Arity erfordert separate Wörterbücher; einstellige Typen sind ideal für grundlegende Anwendungsfälle.
  • Zukünftige native Unterstützung könnte durch schrittweise Verbesserungen kommen, ohne die Leistung zu beeinträchtigen.
  • Die Emulation ermöglicht saubere Implementierungen von map, flatMap und ähnlichen Funktionen ohne Code-Duplikation über Array, Set, Promise und mehr.

— Editorial Team

Advertisement 728x90

Weiterlesen