0Pricing
TypeScript Academy · 课时

轻量级 HKT 模式

实现 Kind/URI 去函数化技巧。

轻量级 HKT 模式 是 CoddyKit 上的免费 TypeScript Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 TypeScript Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 TypeScript Academy 课程共包含 4 节课。

去函数化

这种编码背后的技巧有一个名称:去函数化。我们不使用真正的高阶类型函数,而是用一个标签表示每个函数,再通过一次一阶查找解析它。URItoKind 注册表就是这张查找表。

type Kind<F extends URIS, A> = URItoKind<A>[F];
// One lookup replaces true type-function application

Kind 类型

Kind<F, A> 是这一模式对外呈现的形式。可以将它理解为“将构造器 F 应用于 A”。它用一个清晰的名称隐藏了注册表索引操作。

type A = Kind<"Array", number>;  // number[]
type B = Kind<"Option", string>; // string | null

将 F 限制为 URIS

F 必须是已注册的统一资源标识符。使用 extends URIS 对其进行约束,这样只能使用有效标签,拼写错误也会变成编译错误。

type Kind<F extends URIS, A> = URItoKind<A>[F];

// Kind<"Arrey", number> -> error, not a valid URI

编写泛型签名

现在我们可以编写之前无法实现的 Functor 签名。将每个 F<A> 替换为 Kind<F, A> 后,编译器就能接受它。

interface Functor<F extends URIS> {
  map<A, B>(fa: Kind<F, A>, f: (a: A) => B): Kind<F, B>;
}

实际解析

当您将 F 固定为具体的统一资源标识符时,Kind 就会解析为真正的容器类型。Functor<"Array"> 包含一个从 A[] 到 B[] 的 map。

type ArrayFunctor = Functor<"Array">;
// map: <A, B>(fa: A[], f: (a: A) => B) => B[]

多个类型参数

有些构造器接受两个参数,例如 Either<E, A>。fp-ts 使用 URItoKind2 和 Kind2<F, E, A> 扩展了这一模式,以支持 * -> * -> * 这种种类。只需添加更多注册表,这一思路就可以继续扩展。

interface URItoKind2<E, A> {
  Either: { left: E } | { right: A };
}
type Kind2<F extends URIS2, E, A> = URItoKind2<E, A>[F];

fp-ts 约定

fp-ts 确立了本课程始终遵循的约定:每种数据类型使用一个 URI 字符串,通过声明合并扩展 URItoKind 注册表,并使用 Kind 进行解析。识别这一约定后,您就能读懂 fp-ts 和 Effect 的类型签名。

// In fp-ts you will see exactly:
// declare module "fp-ts/HKT" { interface URItoKind<A> { ... } }

一个实例记录

抽象的“实例”是一个针对特定统一资源标识符实现该抽象的值。实例携带具体的 map。我们将在下一课编写数组实例,这里先看看它的形状。

declare const arrayFunctor: Functor<"Array">;
const out = arrayFunctor.map([1, 2, 3], n => n * 2); // number[]

为什么称为“轻量级”

它被称为轻量级 HKT 模式,是因为它不需要修改编译器,也不需要特殊语法,只需要接口和索引访问。代价是需要一些额外形式(统一资源标识符、注册表),换来真正支持容器泛型的代码。

type Kind<F extends URIS, A> = URItoKind<A>[F];
// No magic, just a typed lookup table

局限性

这种模式比较冗长,而且错误消息可能难以理解,因为其中提到的是编码本身,而不是您的领域。它还要求每个容器都完成注册。对大多数应用来说,您只需使用已经实现这种模式的库,而不是亲自构建它。

// Trade-off: real abstraction power vs. encoding ceremony

整合起来

完整的轻量级 HKT 配方是:定义一个统一资源标识符,将其注册到 URItoKind,使用 Kind<F, A> 定义抽象,并为每个统一资源标识符提供实例。这就是 TypeScript 泛型函数式库背后的全部工具。

type Kind<F extends URIS, A> = URItoKind<A>[F];
interface Functor<F extends URIS> {
  map<A, B>(fa: Kind<F, A>, f: (a: A) => B): Kind<F, B>;
}

快速检查

测试您对轻量级 HKT 模式的理解。

回顾

您构建了轻量级 HKT 模式。

  • 去函数化将类型函数应用替换为查找。
  • Kind<F, A> 将统一资源标识符和参数解析为具体类型。
  • 泛型签名使用 Kind,而不是 F<A>。
  • 它可以通过 Kind2 扩展到两个参数,并且是 fp-ts 所采用的约定。

下一步:使用真实实例实现泛型 map。

常见问题解答

「轻量级 HKT 模式」课时是免费的吗?

是的 — 「轻量级 HKT 模式」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 TypeScript Academy 课程的其余内容,请升级到 CoddyKit PRO。 TypeScript Academy 课程共包含 4 节课。

「轻量级 HKT 模式」这节课中我会学到什么?

实现 Kind/URI 去函数化技巧。 你通过在浏览器中直接运行的动手代码来练习 TypeScript Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 TypeScript Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 TypeScript Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「轻量级 HKT 模式」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 TypeScript Academy 课中编写并运行代码吗?

能。每节 TypeScript Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. TypeScript 中的 HKT 问题
  2. 定义类型构造器
  3. 轻量级 HKT 模式
  4. 泛型函子与映射器
← 返回 TypeScript Academy