Skip to content

Either 与 Maybe:用类型表达“可能失败”的计算

——替代 if (data) { ... } else { ... },让错误处理变得“可组合”

null 是十亿美元的错误,
EitherMaybe 是函数式的疫苗。”

一、问题:脆弱的 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

特性MaybeEither
用途处理 null/undefined处理明确的成功/失败
分支Just / NothingRight / 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-tsOption (Maybe), Either
SanctuaryMaybe, Either
RamdaMaybe via ramda-fantasy
crocksMaybe, 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 去“防御”错误,
而是用 mapchainfold 去“变换”和“处理”它。

这不仅是语法变化,
更是思维模式的升级

从“希望不失败” → 到“设计失败路径”。

这才是健壮系统的根基。