Either 与 Maybe:用类型表达“可能失败”的计算
——替代 if (data) { ... } else { ... },让错误处理变得“可组合”
“null 是十亿美元的错误,Either 和 Maybe 是函数式的疫苗。”
一、问题:脆弱的 if 分支
js
function getUser(id) {
const user = db.find(id);
if (user && user.profile && user.profile.address) {
return user.profile.address.city;
} else {
return null; // 或抛异常
}
}这种代码:
- 容易遗漏检查(如
user.profile) - 错误信息丢失
- 无法链式调用
- 类型系统难推断
if/else 是命令式控制流,不是可组合的数据结构。
二、Maybe:表达“值可能为空”
核心思想:
值只有两种状态:
Just(value):有值Nothing():无值
实现 Maybe
js
const Maybe = {
of: value =>
value == null ? Nothing() : Just(value)
};
const Just = value => ({
map: f => Maybe.of(f(value)),
chain: f => f(value),
getOrElse: () => value,
toString: () => `Just(${value})`
});
const Nothing = () => ({
map: () => Nothing(),
chain: () => Nothing(),
getOrElse: defaultValue => defaultValue,
toString: () => 'Nothing'
});使用 Maybe 避免空值崩溃
js
const user = Maybe.of({ name: 'Alice', profile: null });
const city = user
.map(u => u.profile)
.map(p => p.address)
.map(a => a.city)
.getOrElse('Unknown');
console.log(city); // 'Unknown' —— 安全!- 一旦遇到
null,后续map自动跳过 - 无需层层
if
三、Either:表达“成功或失败”
核心思想:
计算有两种结果:
Right(value):成功Left(error):失败(可携带错误信息)
实现 Either
js
const Either = {
of: value => Right(value),
left: error => Left(error)
};
const Right = value => ({
map: f => Right(f(value)),
chain: f => f(value),
fold: (failure, success) => success(value),
toString: () => `Right(${value})`
});
const Left = error => ({
map: () => Left(error),
chain: () => Left(error),
fold: (failure, success) => failure(error),
toString: () => `Left(${error})`
});使用 Either 处理可能出错的操作
js
const fetchUser = id =>
id > 0
? Either.of({ id, name: 'User' })
: Either.left('Invalid ID');
const validateAge = user =>
user.age >= 18
? Either.of(user)
: Either.left('Underage');
// 组合多个可能失败的操作
const processUser = id =>
fetchUser(id)
.chain(validateAge) // 只有成功才继续
.map(user => user.name.toUpperCase());
processUser(5).fold(
err => console.error(err),
name => console.log(name)
); // 'USER'四、关键差异:Maybe vs Either
| 特性 | Maybe | Either |
|---|---|---|
| 用途 | 处理 null/undefined | 处理明确的成功/失败 |
| 分支 | Just / Nothing | Right / Left |
| 错误信息 | 无 | 有(可携带) |
| 适用场景 | 数据缺失 | 业务逻辑错误、IO 失败 |
选择指南:
- 从 API 获取数据,可能为
null? →Maybe - 用户输入验证,可能无效? →
Either - 异步请求,可能网络失败? →
Either
五、为什么比 if 更好?—— 可组合性
传统方式:嵌套 if
js
const result = api.getData();
if (result) {
const processed = transform(result);
if (processed.valid) {
save(processed);
} else {
log('Invalid');
}
} else {
retry();
}深层嵌套,难以复用。
函数式方式:链式组合
js
api.getData()
.map(transform)
.chain(save) // save 返回 Either
.fold(
err => err === 'retry' ? retry() : log(err),
data => console.log('Saved:', data)
);每个步骤都是独立函数,可测试、可复用。
六、实际工程应用
1. 表单验证
js
const validateEmail = email =>
email.includes('@')
? Either.of(email)
: Either.left('Invalid email');
const validatePassword = pwd =>
pwd.length >= 8
? Either.of(pwd)
: Either.left('Password too short');
const register = (email, pwd) =>
validateEmail(email)
.chain(validEmail =>
validatePassword(pwd)
.map(validPwd => ({ email: validEmail, pwd: validPwd }))
);2. API 错误处理
js
fetch('/user/1')
.then(r => r.json())
.then(Maybe.of)
.map(extractProfile)
.map(formatForDisplay)
.map(renderUI)
.catch(() => showOffline());七、工具与库推荐
| 库 | 支持 |
|---|---|
| fp-ts | Option (Maybe), Either |
| Sanctuary | Maybe, Either |
| Ramda | Maybe via ramda-fantasy |
| crocks | Maybe, Result (Either) |
TypeScript 示例
ts
type Maybe<T> = { tag: 'Just'; value: T } | { tag: 'Nothing' };
type Either<L, R> = { tag: 'Left'; left: L } | { tag: 'Right'; right: R };类型系统在编译时防止错误!
结语:Either 与 Maybe 是“错误即数据”
它们把“可能失败”从一种运行时意外**, 变成了一个可操作的一等公民。
你不再用 if 去“防御”错误,
而是用 map、chain、fold 去“变换”和“处理”它。
这不仅是语法变化,
更是思维模式的升级:
从“希望不失败” → 到“设计失败路径”。
这才是健壮系统的根基。