TypeScript 函数式编程:进阶篇

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

TypeScript 中的函数式编程:进阶篇

函数式编程(FP)是一种近年来在 JavaScript 和 TypeScript 领域风靡的编程范式。对于已经熟悉基础概念(纯函数、数据不可变性)的开发者来说,一个新的可能性层正在打开:与单子(Monad)、函子(Functor)、应用函子(Applicative)以及复杂组合的协作。在本文中,我们将通过实践示例,在 TypeScript 中解析这些进阶 FP 概念。



1. 不可变性与结构共享

不可变性是 FP 的基石。在 TypeScript 中,我们可以通过 readonly 和 ReadonlyArray 在类型层面保证不可变性。然而,为了高效处理大型数据结构(例如 Redux 存储),我们会使用结构共享(Structural Sharing)——像 Immer 或 Immutable.js 这样的库。

考虑一个使用 readonly 以及一个不通过突变来更新嵌套对象的函数的例子:

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 (原始对象未改变)console.log(updatedUser.address.city); // LA

注意:我们创建了一个新对象,但复用了未改变的部分(如 id 和 name)。这是手动实现的结构共享。对于深层嵌套,最好使用库。



2. 函子及其实现

函子(Functor)是一个实现了 map 方法的容器,它允许将一个函数应用于容器内的值,同时不破坏其结构。在 TypeScript 中,一个典型的函子是 Array。但我们也可以创建自己的函子,例如 Maybe,来避免 null 和 undefined 导致的错误。

让我们实现一个简单的 Maybe 函子:

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

// 使用示例const 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"

Maybe 函子允许安全地链式调用函数,而无需担心 null。每个 map 都返回一个新的 Maybe,从而保留了上下文。



3. 单子:带有副作用的操作链

单子(Monad)是一个同时实现了 flatMap 方法(通常称为 bind 或 chain)的函子。它允许“解包”嵌套的容器。单子的一个经典例子是 Promise 或 Array。让我们看看用于错误处理(不使用异常)的 Either 单子。

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

博客

书籍推荐