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.
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:
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:
- 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,flatMapund ähnlichen Funktionen ohne Code-Duplikation überArray,Set,Promiseund mehr.
— Editorial Team
Noch keine Kommentare.