🔥代数数据类型(ADT):如何用 JS 模拟 Sum 与 Product 类型?
——Either(Sum)、Tuple(Product)的实现与模式匹配
“JavaScript 没有类型系统?
不,它只是把类型构造的权力,交给了程序员。”
一、什么是代数数据类型(Algebraic Data Types, ADT)?
ADT 是函数式语言(如 Haskell、ML)的核心类型系统,由两种基本构造组成:
| 类型 | 数学类比 | 含义 |
|---|---|---|
| Sum Type | A + B | “或”关系:值是 A 或 B |
| Product Type | A × B | “且”关系:值是 A 且 B |
它们被称为“代数”,因为其可能值的数量符合加法与乘法。
二、Product Type(乘积类型):A 且 B
核心:组合多个值
最典型的 Product Type 是 Tuple 或 Record。
实现 Tuple
js
const Tuple = (a, b) => ({
a, b,
map: f => Tuple(a, f(b)), // 通常映射第二个值
toString: () => `(${a}, ${b})`
});使用示例
js
const userWithAge = Tuple("Alice", 30);
// 类型:String × Number一个 Tuple(String, Number) 有:
|String| × |Number|种可能值(巨大乘积)
这就是“乘积”的由来。
Record(对象)也是 Product Type
{ name: "Alice", age: 30, active: true }
// 类型:String × Number × Boolean三、Sum Type(和类型):A 或 B
核心:互斥的选择
最典型的 Sum Type 是 Either 或 Maybe。
实现 Either(Left + Right)
js
const Left = value => ({
value,
map: () => Left(value),
chain: () => Left(value),
fold: (leftFn, rightFn) => leftFn(value),
toString: () => `Left(${value})`
});
const Right = value => ({
value,
map: f => Right(f(value)),
chain: f => f(value),
fold: (leftFn, rightFn) => rightFn(value),
toString: () => `Right(${value})`
});
const Either = { Left, Right };使用示例
js
const result = Math.random() > 0.5
? Either.Right(42)
: Either.Left("Error");
// 类型:Number + String一个 Either(Number, String) 有:
|Number| + |String|种可能值
这就是“和”的由来。
Maybe 是 Sum Type
js
const Maybe = {
Just: value => ({ tag: 'Just', value }),
Nothing: () => ({ tag: 'Nothing' })
};
// 类型:A + Unit(Nothing 是单值类型)四、为什么叫“代数”?—— 类型的算术
类型即数字
Boolean:2 个值 → 大小为 2Unit(如null):1 个值 → 大小为 1Void(无值):0 个值 → 大小为 0
构造规则
- Product:
A × B→|A| × |B| - Sum:
A + B→|A| + |B|
示例:RGB 颜色
js
// Product Type
const Color = (r, g, b) => ({ r, g, b });
// 每个分量 0-255 → 256 值
// 总可能值:256 × 256 × 256 = 16,777,216示例:结果类型
// Sum Type
type Result = Success Value | Failure Error
// |Result| = |Value| + |Error|五、模式匹配:解构 ADT
ADT 的强大在于可预测的结构,适合模式匹配。
JavaScript 中的“模式匹配”实现
方法 1:fold(推荐)
js
const handleResult = result =>
result.fold(
error => console.error("Failed:", error),
data => console.log("Success:", data)
);
handleResult(Either.Right("ok")); // Success: ok
handleResult(Either.Left("404")); // Failed: 404方法 2:tag 检查(TypeScript 风格)
js
const maybeValue = Maybe.Just(5);
if (maybeValue.tag === 'Just') {
console.log(maybeValue.value * 2);
} else {
console.log("No value");
}方法 3:match 函数
js
const match = (adt, patterns) => adt.fold(patterns.Left, patterns.Right);
match(Either.Right(10), {
Left: e => `Error: ${e}`,
Right: x => `Value: ${x}`
}); // "Value: 10"六、真实工程应用
1. API 响应建模
js
const Response = {
Loading: () => ({ tag: 'Loading' }),
Success: data => ({ tag: 'Success', data }),
Error: msg => ({ tag: 'Error', msg })
};
// 在 React 中使用
const render = state =>
state.tag === 'Loading' ? <Spinner />
: state.tag === 'Success' ? <Data data={state.data} />
: <Error msg={state.msg} />;完美替代 isLoading、data、error 三个分散状态。
2. 事件流(Redux Action)
text
const Action = {
ADD: payload => ({ type: 'ADD', payload }),
DELETE: id => ({ type: 'DELETE', id }),
FETCH_START: () => ({ type: 'FETCH_START' }),
FETCH_SUCCESS: data => ({ type: 'FETCH_SUCCESS', data })
};
// reducer 中模式匹配
switch(action.type) {
case 'ADD': ...
case 'DELETE': ...
}七、TypeScript 中的 ADT(更安全)
ts
// Sum Type
type Result<T, E> =
| { success: true; value: T }
| { success: false; error: E };
// Product Type
interface User {
name: string;
age: number;
}
// 使用
const handle = (result: Result<string, string>) => {
if (result.success) {
result.value.toUpperCase(); // 类型自动推断
} else {
console.error(result.error);
}
};八、为什么 ADT 重要?
清晰表达业务逻辑
- “用户状态”是
LoggedIn | LoggedOut - “加载状态”是
Loading | Success | Error
编译时检查(TS)
- 避免无效状态(如
loading: true且data: null但error存在)
可组合性
- ADT + Functor + Applicative + Monad = 强大类型系统
结语:ADT 是“类型即设计”
它强迫你思考:这个值可能是什么?有哪些状态?
Either 不只是一个错误处理工具,
它是 Success + Failure 的代数表达。
Tuple 不只是两个值,
它是 A × B 的乘积构造。
在 JavaScript 这个“灵活”的语言中,
ADT 是你构建健壮、可维护系统的基石。