泛型函子与映射器
编写适用于任意容器的泛型 map 函数。
泛型函子与映射器 是 CoddyKit 上的免费 TypeScript Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 TypeScript Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 TypeScript Academy 课程共包含 4 节课。
一个泛型函子
现在我们要发挥这种编码的作用:实现一个适用于任意已注册容器的泛型 map。以统一资源标识符为参数的 Functor 接口声明了一个方法。
interface Functor<F extends URIS> {
readonly URI: F;
map<A, B>(fa: Kind<F, A>, f: (a: A) => B): Kind<F, B>;
}数组实例
一个实例会针对某个统一资源标识符实现 map。数组实例使用内置的数组方法来执行映射。它的 Kind<"Array", A> 会解析为 A[]。
const arrayFunctor: Functor<"Array"> = {
URI: "Array",
map: (fa, f) => fa.map(f)
};使用数组实例
在数组实例上调用 map 会转换每个元素,同时保留数组容器类型。
const doubled = arrayFunctor.map([1, 2, 3], n => n * 2);
// doubled: number[] -> [2, 4, 6]选项类型
将选项定义为一个值或 null,然后注册它。它的 map 仅在存在值时应用函数,否则传递 null。
interface URItoKind<A> { Option: A | null }
type Option<A> = A | null;选项实例
选项实例会保留选项容器:函数会作用于其中的值,而 null 仍然是 null。
const optionFunctor: Functor<"Option"> = {
URI: "Option",
map: (fa, f) => (fa === null ? null : f(fa))
};使用选项实例
现在,同样形式的 map 可以作用于完全不同的容器。存在的值会被转换,不存在的值则保持不变。
const a = optionFunctor.map(5, n => n + 1); // 6
const b = optionFunctor.map(null, n => n + 1); // null一个抽象,多种容器
这就是这种做法的价值所在。arrayFunctor 和 optionFunctor 共享同一个 Functor 接口。针对 Functor<F> 编写的代码无需修改就能同时适用于两者。
function bumpAll<F extends URIS>(
F: Functor<F>,
fa: Kind<F, number>
): Kind<F, number> {
return F.map(fa, n => n + 1);
}调用泛型函数
bumpAll 不知道也不关心它接收的是哪种容器。传入实例和数据,结果就会保留容器类型。
const arr = bumpAll(arrayFunctor, [1, 2]); // number[] -> [2, 3]
const opt = bumpAll(optionFunctor, 9); // number | null -> 10函子定律
正确的函子必须遵守两条定律:映射恒等函数不会产生任何变化;依次映射两个函数,结果等于映射它们的组合。上面的数组实例和选项实例都满足这些定律。
// map(fa, x => x) === fa
// map(map(fa, f), g) === map(fa, x => g(f(x)))添加更多实例
由于这种抽象是开放的,新容器只需注册一个统一资源标识符并提供一个实例即可接入。树、结果、任务都可以通过同样的泛型代码变得可映射。
// Register URI, implement Functor<"Tree">,
// and bumpAll works on trees too - no edits to bumpAll为什么这很重要
泛型函子是 TypeScript 函数式抽象的入口:应用函子、单子、遍历等抽象都建立于此。每一种抽象都是围绕 Kind<F, A> 定义的接口,并为每种容器提供实例。现在您已经理解了让 fp-ts 和 Effect 成为可能的机制。
interface Monad<F extends URIS> extends Functor<F> {
of<A>(a: A): Kind<F, A>;
chain<A, B>(fa: Kind<F, A>, f: (a: A) => Kind<F, B>): Kind<F, B>;
}快速检查
测试您对泛型函子的理解。
回顾
您编写了适用于泛型容器的 map。
Functor<F>声明了针对Kind<F, A>的map。- 数组实例和选项实例分别针对各自的统一资源标识符实现了它。
- 像
bumpAll这样的泛型代码适用于任何已注册的容器。 - 函子定律让实例保持良好行为,更丰富的抽象也建立在此基础之上。
下一课是第 24 课:构建类型安全的解析器。
常见问题解答
「泛型函子与映射器」课时是免费的吗?
是的 — 「泛型函子与映射器」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 TypeScript Academy 课程的其余内容,请升级到 CoddyKit PRO。 TypeScript Academy 课程共包含 4 节课。
「泛型函子与映射器」这节课中我会学到什么?
编写适用于任意容器的泛型 map 函数。 你通过在浏览器中直接运行的动手代码来练习 TypeScript Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 TypeScript Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 TypeScript Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「泛型函子与映射器」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 TypeScript Academy 课中编写并运行代码吗?
能。每节 TypeScript Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。