编程充满挑战。当我们构建库和应用时,会依赖许多工具来应对复杂性,让日常开发更高效。Effect 为 TypeScript 编程提供了一种全新的思路。
Effect 是一套工具生态系统,能帮助你打造更优质的应用程序和库。与此同时,你还能更深入地了解 TypeScript 语言,学会如何利用类型系统让程序更可靠、更易于维护。
在 "传统" 的 TypeScript 开发中,不使用 Effect 时,我们编写的代码会默认函数要么执行成功,要么抛出异常。例如:
TypeScript
const divide = (a: number, b: number): number => {
if (b === 0) {
throw new Error("Cannot divide by zero")
}
return a / b
}
从类型定义中,我们完全无法得知这个函数可能会抛出异常。只能通过阅读代码才能发现这一点。当代码库中只有一个这样的函数时,这似乎不算大问题,但当函数数量达到数百个甚至数千个时,问题就会逐渐累积。我们很容易忘记某个函数可能抛出异常,也容易忽略对这些异常的处理。
通常,我们会选择最 "简单" 的方式 ------ 用try/catch块包裹函数。这是防止程序崩溃的第一步,但并不能让我们更轻松地管理或理解复杂的应用 / 库。我们可以做得更好。
TypeScript 中最强大的工具之一就是编译器。它是抵御 bug、领域错误和一般复杂性的第一道防线。
Effect 模式
尽管 Effect 是一个包含多种工具的庞大生态系统,但如果要提炼其核心思想,那就是:
Effect 最独特的洞察是,我们可以利用类型系统追踪错误和上下文,而不仅仅是像上面的除法示例中那样只追踪成功值。
以下是采用 Effect 模式重写的同一个除法函数:
TypeScript
import { Effect } from "effect"
const divide = (
a: number,
b: number
): Effect.Effect<number, Error, never> =>
b === 0
? Effect.fail(new Error("Cannot divide by zero"))
: Effect.succeed(a / b)
通过这种方式,函数不再抛出异常。相反,错误会被当作值来处理,就像成功值一样可以传递。类型签名也清晰地表明了三点:
-
函数返回的成功值类型(
number)。 -
可能发生的错误类型(
Error)。 -
所需的额外上下文或依赖(
never表示无依赖)。┌─── 产生number类型的值
│ ┌─── 可能抛出Error类型错误
│ │ ┌─── 无需任何依赖
▼ ▼ ▼
Effect<number, Error, never>
此外,通过追踪上下文,你可以为函数提供额外信息,而无需将所有内容都作为参数传入。例如,在测试时,你可以用模拟实现替换实际的外部服务,而无需修改任何核心业务逻辑。
无需重复造轮子
TypeScript 应用代码经常会反复解决相同的问题。与外部服务、文件系统、数据库等交互,是所有应用开发者都会遇到的常见问题。
Effect 提供了丰富的库生态系统,为这些问题提供了标准化解决方案。你可以直接使用这些库构建应用,也可以基于它们打造自己的库。
错误处理、调试、追踪、异步 / 承诺(promises)、重试、流处理、并发、缓存、资源管理等诸多挑战,都能通过 Effect 得到妥善管理。你无需重新设计这些问题的解决方案,也不必安装大量依赖。Effect 将众多功能整合在一起,解决了原本需要安装多个不同 API 的依赖才能应对的问题。
解决实际问题
Effect 深受 Scala、Haskell 等其他语言优秀成果的启发。但需要明确的是,Effect 的目标是成为一套实用工具集,它致力于解决开发者在 TypeScript 中构建应用和库时面临的真实日常问题。
享受构建与学习的过程
学习 Effect 充满乐趣。Effect 生态中的许多开发者不仅在日常工作中用它解决实际问题,还在探索前沿思路,力求让 TypeScript 成为最实用的语言。
你不必一次性掌握 Effect 的所有功能,可以从生态系统中最适合解决你当前问题的部分开始。Effect 是一套工具集,你可以根据自己的用例灵活选择。不过,随着代码库中 Effect 的使用率越来越高,你可能会发现自己想要探索更多生态功能!
Effect 的概念对你来说可能是全新的,一开始可能无法完全理解。这完全正常。慢慢来,仔细阅读文档,努力掌握核心概念 ------ 随着你深入了解 Effect 生态中更高级的工具,这些付出日后必将带来回报。