Effect-TS值处理模式Skill effect-patterns-value-handling

这个技能提供了 Effect-TS 中处理值的两个核心模式:处理 None 和 Some 值的模式,以及可选链与组合模式。它帮助开发者避免空值错误,实现类型安全编程,提升代码可读性和维护性。关键词:Effect-TS, Option类型, 模式匹配, 值处理, TypeScript, 函数式编程。

前端开发 0 次安装 0 次浏览 更新于 3/8/2026

name: effect-patterns-value-handling description: Effect-TS 值处理模式。在 Effect-TS 应用程序中处理值时使用。

Effect-TS 模式:值处理

这个技能提供了 2 个精选的 Effect-TS 值处理模式。 在以下任务中使用此技能:

  • 值处理
  • Effect-TS 应用程序中的最佳实践
  • 现实世界中的模式和解决方案

🟡 中级模式

可选模式 1:处理 None 和 Some 值

规则: 使用 Option 表示可能不存在的值,用类型安全的 Option 替换 null/undefined,强制显式处理。

好例子:

这个例子演示了 Option 处理模式。

import { Effect, Option } from "effect";

interface User {
  id: string;
  name: string;
  email: string;
}

interface Profile {
  bio: string;
  website?: string;
  location?: string;
}

const program = Effect.gen(function* () {
  console.log(
    `
[选项处理] None/Some 值和模式匹配
`
  );

  // 示例 1:创建 Options
  console.log(`[1] 创建 Option 值:
`);

  const someValue: Option.Option<string> = Option.some("数据");
  const noneValue: Option.Option<string> = Option.none();

  const displayOption = <T,>(opt: Option.Option<T>, label: string) =>
    Effect.gen(function* () {
      if (Option.isSome(opt)) {
        yield* Effect.log(`${label}: Some(${opt.value})`);
      } else {
        yield* Effect.log(`${label}: None`);
      }
    });

  yield* displayOption(someValue, "someValue");
  yield* displayOption(noneValue, "noneValue");

  // 示例 2:从可空值创建
  console.log(`
[2] 将可空值转换为 Option:
`);

  const possiblyNull = (shouldExist: boolean): string | null =>
    shouldExist ? "找到" : null;

  const toOption = (value: string | null | undefined): Option.Option<string> =>
    value ? Option.some(value) : Option.none();

  const opt1 = toOption(possiblyNull(true));
  const opt2 = toOption(possiblyNull(false));

  yield* displayOption(opt1, "toOption(找到)");
  yield* displayOption(opt2, "toOption(null)");

  // 示例 3:在 Option 上进行模式匹配
  console.log(`
[3] 使用 match() 进行模式匹配:
`);

  const userId: Option.Option<string> = Option.some("user-123");

  const message = Option.match(userId, {
    onSome: (id) => `用户 ID: ${id}`,
    onNone: () => "未找到用户",
  });

  yield* Effect.log(`[匹配] ${message}`);

  const emptyUserId: Option.Option<string> = Option.none();

  const emptyMessage = Option.match(emptyUserId, {
    onSome: (id) => `用户 ID: ${id}`,
    onNone: () => "未找到用户",
  });

  yield* Effect.log(`[匹配] ${emptyMessage}
`);

  // 示例 4:使用 map 转换值
  console.log(`[4] 使用 map() 转换值:
`);

  const userCount: Option.Option<number> = Option.some(42);

  const doubled = Option.map(userCount, (count) => count * 2);

  yield* displayOption(doubled, "加倍");

  // 链式映射
  const email: Option.Option<string> = Option.some("user@example.com");

  const domain = Option.map(email, (e) =>
    e.split("@")[1] ?? "未知"
  );

  yield* displayOption(domain, "邮箱域名");

  // 示例 5:使用 flatMap 链式操作
  console.log(`
[5] 使用 flatMap() 链式操作:
`);

  const findUser = (id: string): Option.Option<User> =>
    id === "user-1"
      ? Option.some({ id, name: "Alice", email: "alice@example.com" })
      : Option.none();

  const getProfile = (userId: string): Option.Option<Profile> =>
    userId === "user-1"
      ? Option.some({ bio: "开发者", website: "alice.dev" })
      : Option.none();

  const userId2 = Option.some("user-1");

  // 链式操作:userId -> user -> profile
  const profileChain = Option.flatMap(userId2, (id) =>
    Option.flatMap(findUser(id), (user) =>
      getProfile(user.id)
    )
  );

  const profileResult = Option.match(profileChain, {
    onSome: (profile) => `简介: ${profile.bio}, 网站: ${profile.website}`,
    onNone: () => "未找到简介",
  });

  yield* Effect.log(`[链] ${profileResult}
`);

  // 示例 6:使用 getOrElse 提供后备值
  console.log(`[6] 使用 getOrElse() 设置默认值:
`);

  const optionalStatus: Option.Option<string> = Option.none();

  const status = Option.getOrElse(optionalStatus, () => "未知");

  yield* Effect.log(`[默认] 状态: ${status}`);

  // 实际值
  const knownStatus: Option.Option<string> = Option.some("活跃");

  const realStatus = Option.getOrElse(knownStatus, () => "未知");

  yield* Effect.log(`[值] 状态: ${realStatus}
`);

  // 示例 7:使用谓词过滤
  console.log(`[7] 使用条件过滤:
`);

  const ageOption: Option.Option<number> = Option.some(25);

  const isAdult = Option.filter(ageOption, (age) => age >= 18);

  yield* displayOption(isAdult, "成人检查 (25)");

  const ageOption2: Option.Option<number> = Option.some(15);

  const isAdult2 = Option.filter(ageOption2, (age) => age >= 18);

  yield* displayOption(isAdult2, "成人检查 (15)");

  // 示例 8:多个 Options(所有都存在?)
  console.log(`
[8] 组合多个 Options:
`);

  const firstName: Option.Option<string> = Option.some("John");
  const lastName: Option.Option<string> = Option.some("Doe");
  const middleName: Option.Option<string> = Option.none();

  // 所有三个都存在?
  const allPresent = Option.all([firstName, lastName, middleName]);

  yield* displayOption(allPresent, "所有存在");

  // 仅两个
  const twoPresent = Option.all([firstName, lastName]);

  yield* displayOption(twoPresent, "两个存在");

  // 示例 9:将 Option 转换为错误
  console.log(`
[9] 将 Option 转换为结果/错误:
`);

  const optionalConfig: Option.Option<{ apiKey: string }> = Option.none();

  const configOrError = Option.match(optionalConfig, {
    onSome: (config) => config,
    onNone: () => {
      throw new Error("未找到配置");
    },
  });

  // 在实际代码中,会捕获错误
  const result = Option.match(optionalConfig, {
    onSome: (config) => ({ success: true, value: config }),
    onNone: () => ({ success: false, error: "config-not-found" }),
  });

  yield* Effect.log(`[转换] ${JSON.stringify(result)}
`);

  // 示例 10:在业务逻辑中使用 Option
  console.log(`[10] 实践:可选用户设置:
`);

  const userSettings: Option.Option<{
    theme: string;
    notifications: boolean;
  }> = Option.some({
    theme: "深色",
    notifications: true,
  });

  const getTheme = Option.map(userSettings, (s) => s.theme);
  const theme = Option.getOrElse(getTheme, () => "浅色"); // 默认

  yield* Effect.log(`[设置] 主题: ${theme}`);

  // 无设置
  const noSettings: Option.Option<{ theme: string; notifications: boolean }> =
    Option.none();

  const noTheme = Option.map(noSettings, (s) => s.theme);
  const defaultTheme = Option.getOrElse(noTheme, () => "浅色");

  yield* Effect.log(`[默认] 主题: ${defaultTheme}`);
});

Effect.runPromise(program);

原理:

Option 实现空值安全编程:

  • Some(value):值存在
  • None:值不存在
  • 模式匹配:显式处理两种情况
  • 链式操作:安全组合操作
  • 后备值:默认值
  • 转换:Option ↔ 错误

模式:使用 Option.isSome()Option.isNone()match()map()flatMap()


空值/未定义导致广泛错误:

问题 1:十亿美元错误

  • Tony Hoare 在 1965 年于 ALGOL 中发明了 null
  • 创造了“十亿美元错误”
  • 90% 的安全漏洞涉及空值处理

问题 2:未定义行为

  • user.profile.name - 任何属性都可能为 null
  • 运行时错误:“无法读取未定义的属性 ‘name’”
  • 无编译时警告
  • 生产环境崩溃

问题 3:静默失败

  • 函数在失败时返回 null
  • 调用者未检查
  • 将 null 当作值使用
  • 下游状态损坏

问题 4:条件地狱

if (user !== null && user.profile !== null && user.profile.name !== null) {
  // 执行操作
}

解决方案:

Option 类型

  • Some(value) = 值存在
  • None = 值不存在
  • 类型系统强制检查
  • 无法静默空值检查

模式匹配

  • Option.match()
  • 显式处理两种情况
  • 如果遗漏,编译器会警告

链式操作

  • option.map().flatMap().match()
  • 操作管道
  • 设计上安全处理空值


🟠 高级模式

可选模式 2:可选链与组合

规则: 使用 Option 组合器(map、flatMap、ap)组合可能失败的操作,创建可读且可维护的管道。

好例子:

这个例子演示了可选链模式。

import { Effect, Option, pipe } from "effect";

interface User {
  id: string;
  name: string;
  email: string;
}

interface Profile {
  bio: string;
  website?: string;
  avatar?: string;
}

interface Settings {
  theme: "light" | "dark";
  notifications: boolean;
  language: string;
}

const program = Effect.gen(function* () {
  console.log(`
[可选链] 组合 Option 操作
`);

  // 示例 1:使用 map 的简单链
  console.log(`[1] 使用 map() 链式转换:
`);

  const userId: Option.Option<string> = Option.some("user-42");

  const userDisplayId = Option.map(userId, (id) => `User#${id}`);

  const idMessage = Option.match(userDisplayId, {
    onSome: (display) => display,
    onNone: () => "无用户 ID",
  });

  yield* Effect.log(`[链 1] ${idMessage}`);

  // 链式映射
  const email: Option.Option<string> = Option.some("alice@example.com");

  const emailParts = pipe(
    email,
    Option.map((e) => e.toLowerCase()),
    Option.map((e) => e.split("@")),
    Option.map((parts) => parts[0]) // 用户名
  );

  const username = Option.getOrElse(emailParts, () => "未知");

  yield* Effect.log(`[用户名] ${username}
`);

  // 示例 2:使用 flatMap 链式返回 Option 的操作
  console.log(`[2] 使用 flatMap() 链式操作:
`);

  const findUser = (id: string): Option.Option<User> =>
    id === "user-42"
      ? Option.some({
          id,
          name: "Alice",
          email: "alice@example.com",
        })
      : Option.none();

  const getProfile = (userId: string): Option.Option<Profile> =>
    userId === "user-42"
      ? Option.some({
          bio: "软件工程师",
          website: "alice.dev",
          avatar: "https://example.com/avatar.jpg",
        })
      : Option.none();

  const userProfile = pipe(
    Option.some("user-42"),
    Option.flatMap((id) => findUser(id)),
    Option.flatMap((user) => getProfile(user.id))
  );

  const profileInfo = Option.match(userProfile, {
    onSome: (profile) => `简介: ${profile.bio}, 网站: ${profile.website}`,
    onNone: () => "未找到简介",
  });

  yield* Effect.log(`[简介] ${profileInfo}
`);

  // 示例 3:复杂管道
  console.log(`[3] 复杂管道(用户 → 简介 → 设置 → 主题):
`);

  const getSettings = (userId: string): Option.Option<Settings> =>
    userId === "user-42"
      ? Option.some({
          theme: "深色",
          notifications: true,
          language: "en",
        })
      : Option.none();

  const userTheme = pipe(
    Option.some("user-42"),
    Option.flatMap((id) => findUser(id)),
    Option.flatMap((user) => getSettings(user.id)),
    Option.map((settings) => settings.theme)
  );

  const theme = Option.getOrElse(userTheme, () => "浅色");

  yield* Effect.log(`[主题] ${theme}`);

  // 即使任何步骤为 None,结果也为 None
  const invalidUserTheme = pipe(
    Option.some("invalid-user"),
    Option.flatMap((id) => findUser(id)),
    Option.flatMap((user) => getSettings(user.id)),
    Option.map((settings) => settings.theme)
  );

  const invalidTheme = Option.getOrElse(invalidUserTheme, () => "浅色");

  yield* Effect.log(`[默认主题] ${invalidTheme}
`);

  // 示例 4:使用 ap() 组合独立的 Options
  console.log(`[4] 使用 ap() 组合值:
`);

  const firstName: Option.Option<string> = Option.some("John");
  const lastName: Option.Option<string> = Option.some("Doe");

  // 创建一个包裹在 Option 中的函数
  const combineNames = (first: string) => (last: string) =>
    `${first} ${last}`;

  const fullName = pipe(
    Option.some(combineNames),
    Option.ap(firstName),
    Option.ap(lastName)
  );

  const name = Option.getOrElse(fullName, () => "未知");

  yield* Effect.log(`[组合] ${name}`);

  // 如果任何为 None
  const noLastName: Option.Option<string> = Option.none();

  const incompleteName = pipe(
    Option.some(combineNames),
    Option.ap(firstName),
    Option.ap(noLastName)
  );

  const incompleteFull = Option.getOrElse(incompleteName, () => "不完整");

  yield* Effect.log(`[不完整] ${incompleteFull}
`);

  // 示例 5:使用 traverse 处理集合
  console.log(`[5] 处理集合(traverse):
`);

  const userIds: string[] = ["user-42", "user-99", "user-1"];

  // 尝试加载所有用户
  const allUsers = Option.all(
    userIds.map((id) => findUser(id))
  );

  const usersMessage = Option.match(allUsers, {
    onSome: (users) => `加载了 ${users.length} 个用户`,
    onNone: () => "未找到某些用户",
  });

  yield* Effect.log(`[遍历] ${usersMessage}
`);

  // 示例 6:使用 orElse 进行多选项回退
  console.log(`[6] 使用 orElse() 的回退链:
`);

  const getPrimaryEmail = (): Option.Option<string> => Option.none();
  const getSecondaryEmail = (): Option.Option<string> =>
    Option.some("backup@example.com");
  const getTertiaryEmail = (): Option.Option<string> =>
    Option.some("tertiary@example.com");

  const email1 = pipe(
    getPrimaryEmail(),
    Option.orElse(() => getSecondaryEmail()),
    Option.orElse(() => getTertiaryEmail())
  );

  const contactEmail = Option.getOrElse(email1, () => "no-email@example.com");

  yield* Effect.log(`[回退] 使用邮箱: ${contactEmail}
`);

  // 示例 7:过滤选项
  console.log(`[7] 使用谓词过滤:
`);

  const age: Option.Option<number> = Option.some(25);

  const canVote = pipe(
    age,
    Option.filter((a) => a >= 18)
  );

  const voteStatus = Option.match(canVote, {
    onSome: () => "可以投票",
    onNone: () => "太年轻无法投票",
  });

  yield* Effect.log(`[过滤] ${voteStatus}`);

  // 链式多个过滤器
  const score: Option.Option<number> = Option.some(85);

  const isAGrade = pipe(
    score,
    Option.filter((s) => s >= 80),
    Option.filter((s) => s < 90)
  );

  const grade = Option.match(isAGrade, {
    onSome: () => "等级 A",
    onNone: () => "不在 A 范围内",
  });

  yield* Effect.log(`[等级] ${grade}
`);

  // 示例 8:实践:数据库查询链
  console.log(`[8] 现实世界:数据库记录链:
`);

  const getRecord = (id: string): Option.Option<{ data: string; nested: { value: number } }> =>
    id === "rec-1"
      ? Option.some({
          data: "内容",
          nested: { value: 42 },
        })
      : Option.none();

  const recordValue = pipe(
    Option.some("rec-1"),
    Option.flatMap((id) => getRecord(id)),
    Option.map((rec) => rec.nested),
    Option.map((nested) => nested.value),
    Option.map((value) => value * 2)
  );

  const finalValue = Option.getOrElse(recordValue, () => 0);

  yield* Effect.log(`[值] ${finalValue}`);

  // 缺失记录
  const missingValue = pipe(
    Option.some("rec-999"),
    Option.flatMap((id) => getRecord(id)),
    Option.map((rec) => rec.nested),
    Option.map((nested) => nested.value),
    Option.map((value) => value * 2)
  );

  const defaultValue = Option.getOrElse(missingValue, () => 0);

  yield* Effect.log(`[默认] ${defaultValue}
`);

  // 示例 9:条件链
  console.log(`[9] 条件路径:
`);

  const loadUserWithFallback = (id: string) =>
    pipe(
      findUser(id),
      Option.flatMap((user) =>
        // 仅当用户存在时获取高级功能
        user.name.includes("Alice")
          ? Option.some({ ...user, isPremium: true })
          : Option.none()
      ),
      Option.orElse(() =>
        // 回退:返回基本用户
        findUser(id)
      )
    );

  const result1 = loadUserWithFallback("user-42");
  const result2 = loadUserWithFallback("user-99");

  yield* Effect.log(
    `[条件 1] ${Option.match(result1, { onSome: (u) => `${u.name}`, onNone: () => "未找到" })}`
  );

  yield* Effect.log(
    `[条件 2] ${Option.match(result2, { onSome: (u) => `${u.name}`, onNone: () => "未找到" })}`
  );
});

Effect.runPromise(program);

原理:

Option 链式操作实现优雅数据流:

  • map:如果值存在则转换
  • flatMap:链式返回 Option 的操作
  • ap:应用包裹在 Option 中的函数
  • traverse:在集合上映射 Option
  • 组合:组合多个链
  • 恢复:提供回退

模式:使用 Option.map()flatMap()ap()、pipe 运算符


嵌套 Option 处理变得复杂:

问题 1:末日金字塔

if (user !== null) {
  if (user.profile !== null) {
    if (user.profile.preferences !== null) {
      if (user.profile.preferences.theme !== null) {
        // 最终执行操作
      }
    }
  }
}

问题 2:重复空值检查

  • 每个步骤都需要自己的检查
  • 代码重复
  • 难以重构
  • 容易引入错误

问题 3:逻辑分散

  • 转换逻辑与空值检查混合
  • 难以理解意图
  • 易出错

解决方案:

Option 链式

  • None 自动流过
  • 仅在 Some 时转换
  • 无需中间检查

组合

  • 清洁组合函数
  • 分离关注点
  • 可重用部件

回退

  • orElse() 用于恢复
  • 链式多个备选方案
  • 优雅降级