Effect<A, E, R> 三个参数怎么读?
Effect.Effect<never, NotFound> 的意思是:永远不会成功,只会以 NotFound 失败。第一次见到 never 出现在成功位置,很容易读懵。
读法是一句话:Effect<A, E, R> 是「需要 R,可能以 E 失败,成功时给出 A」。never 在这里表示「没有」。
先说明一个前置概念:Effect 是一个值,一份「要做什么」的描述,创建时不执行,被运行时才执行,详见 Effect 和 Promise 有什么区别。正因为它是值,编译器在运行之前就能从类型里知道它会产出什么、怎么失败、需要什么。下面是几个具体例子。
常见的几种写法分别是什么意思?
下面几个例子都是真实可写的类型:
const e1: Effect.Effect<number> = Effect.succeed(1)
const e2: Effect.Effect<never, NotFound> = Effect.fail(new NotFound({ id: "1" }))
const e3: Effect.Effect<never> = Effect.die(new Error("bug"))Effect<number>等于Effect<number, never, never>:成功给出数字,没有可预期失败,不需要任何依赖。末尾两个参数有默认值never。Effect<never, NotFound>:永远不会成功,只会以NotFound失败。Effect<never>:既不会成功也没有可预期失败。Effect.die表示一个缺陷,它不进入类型里;可预期失败与缺陷的区别,见错误即类型。
never 为什么表示「没有」?
never 是 TypeScript 里的空集。E = never 表示没有可预期的失败,R = never 表示没有未满足的依赖。
R 里还有东西,程序能运行吗?
不能。当 R 变成 never 时,程序才是自给自足的,才可以被运行。 只要 R 里还有东西,类型系统就在提醒你:还差某个依赖没提供。
// R = Database:还差一个依赖,不能直接运行
declare const getUser: (id: string) =>
Effect.Effect<User, NotFound, Database>
// 提供之后,R 里的 Database 被消掉了
const ready = getUser("1").pipe(
Effect.provideService(Database, { findUser: (id) => Effect.succeed({ id, name: "a" }) })
)这里的 Database 是「服务」,细节见服务。读这段代码时只需要看到一件事:提供依赖,就是把 R 往 never 方向消除的过程。
组合多个 Effect 时,E 和 R 会怎么变?
一个 Effect 在组合的过程中,它的 E 和 R 会逐步汇聚成联合类型,在边缘再一次性处理。所以读一个大程序的类型时,看最外层的 E 和 R 就够了:E 是它可能怎么失败的总和,R 是它还欠着的依赖的总和。
本文的类型示例取自 effect 教程的写法,没有在这篇里逐条重新编译;Effect v4 的 API 与 v3 有不少差别,照抄前请以你安装的版本为准。