Funktionale Programmierung in TypeScript: Fortgeschrittenes Niveau
\n\nFunktionale Programmierung (FP) ist ein Paradigma, das Berechnungen als Auswertung mathematischer Funktionen betrachtet und Zustandsänderungen sowie veränderliche Daten vermeidet. TypeScript, als Erweiterung von JavaScript, bietet ein leistungsstarkes Typsystem, das es ermöglicht, viele FP-Konzepte auf einem neuen Niveau von Sicherheit und Ausdrucksstärke umzusetzen. In diesem Artikel betrachten wir fortgeschrittene Techniken: von strenger Unveränderlichkeit bis zur Arbeit mit Monaden und Funktoren.
\n\nUnveränderlichkeit und Arbeit mit unveränderlichen Strukturen
\n\nUnveränderlichkeit (Immutability) ist ein Eckpfeiler der FP. In TypeScript können wir sie auf Typebene mit Hilfe der Modifikatoren readonly und utilitären Typen garantieren.
// Tiefe Unveränderlichkeit durch ReadonlyDeep\ntype DeepReadonly<T> = {\n readonly [P in keyof T]: DeepReadonly<T[P]>;\n};\n\ninterface User {\n name: string;\n address: { city: string; zip: number };\n}\n\nconst user: DeepReadonly<User> = {\n name: "Alice",\n address: { city: "Moscow", zip: 101000 }\n};\n\n// user.address.city = "SPb"; // Fehler! Eigenschaft ist schreibgeschützt\n\nVerwenden Sie für sicheres Aktualisieren von Daten reine Funktionen, die neue Objekte zurückgeben:
\n\nfunction updateZip(user: DeepReadonly<User>, newZip: number): DeepReadonly<User> {\n return { ...user, address: { ...user.address, zip: newZip } };\n}\n\nconst updatedUser = updateZip(user, 190000);\n\nFunktionskomposition und Currying
\n\nKomposition ermöglicht es, komplexe Transformationen aus einfachen Funktionen aufzubauen. In TypeScript können wir mit Hilfe von Generics typsichere Kombinatoren erstellen.
\n\n// Typ für die Komposition zweier Funktionen\ntype Compose<A, B, C> = (f: (b: B) => C, g: (a: A) => B) => (a: A) => C;\n\nconst compose: Compose<any, any, any> = (f, g) => (x) => f(g(x));\n\n// Beispiel: Umwandlung eines Strings in eine Zahl und Erhöhung um 1\nconst toNumber = (s: string): number => parseInt(s, 10);\nconst increment = (n: number): number => n + 1;\n\nconst parseAndIncrement = compose(increment, toNumber);\nconsole.log(parseAndIncrement("5")); // 6\n\nCurrying verwandelt eine Funktion mit mehreren Argumenten in eine Folge von Funktionen mit einem Argument. Dies macht die Komposition flexibler:
\n\n// Universelle Curry-Funktion mit Typen\nfunction curry<T1, T2, R>(fn: (a: T1, b: T2) => R): (a: T1) => (b: T2) => R {\n return (a: T1) => (b: T2) => fn(a, b);\n}\n\nconst add = (a: number, b: number): number => a + b;\nconst curriedAdd = curry(add);\nconst add5 = curriedAdd(5);\nconsole.log(add5(3)); // 8\n\nFunktoren und Monaden: Arbeit mit Kontexten
\n\nFunktor ist eine Datenstruktur, die die Operation map unterstützt. In TypeScript kann ein Array, Promise oder ein benutzerdefinierter Typ ein Funktor sein.
// Funktor Option (Maybe) für sicheres Arbeiten mit null/undefined\ninterface Option<T> {\n map<U>(fn: (value: T) => U): Option<U>;\n getOrElse(defaultValue: T): T;\n}\n\nclass Some<T> implements Option<T> {\n constructor(private value: T) {}\n map<U>(fn: (value: T) => U): Option<U> {\n return new Some(fn(this.value));\n }\n getOrElse(_: T): T {\n return this.value;\n }\n}\n\nclass None<T> implements Option<T> {\n map<U>(_: (value: T) => U): Option<U> {\n return new None<U>();\n }\n getOrElse(defaultValue: T): T {\n return defaultValue;\n }\n}\n\n// Verwendung\nconst maybeNumber: Option<number> = new Some(42);\nconst result = maybeNumber.map(x => x * 2).getOrElse(0); // 84\n\nMonade erweitert den Funktor um die Methode flatMap (oder bind