30 秒看懂
Richard Eisenberg 使用多态变体、递归类型和列表演示 OCaml 内置的强制转换运算符。随后,他用私有整数类型别名展示模块如何控制值的构造,同时允许调用方将这些值视为整数。
- 1变体的子类型关系较窄多态变体的每个值,都是兼容的较宽变体的合法值。
- 2没有运行时转换列表强制转换由类型系统检查,运行时不会遍历或重新分配列表。
- 3私有表示私有整数别名让模块在构造时维护不变量,同时允许值强制转换回 int。
要点建议
- 当 OCaml 的子类型关系允许时,可以在类型之间进行强制转换。
- 标签和载荷兼容的多态变体展示了一种子类型关系。
- 强制转换可以穿过递归类型和列表,无须在运行时遍历或分配。
- 私有类型别名可以公开底层表示,同时限制构造并维护不变量。
按章节阅读整理后的全文
强制转换与多态变体
Eisenberg 用较窄和较宽的变体介绍内置强制转换语法,并演示载荷为什么必须兼容。
我想探索 OCaml 的子类型关系和强制转换运算符。这是普通 OCaml 的功能,并非 Jane Street 特有的扩展,而且已经存在一段时间。
有些 OCaml 类型的值构成另一类型的值的子集。以多态变体为例:类型 foo 只含标签 `Foo,类型 foo_bar 则含 `Foo 或 `Bar。每个 foo 值也都是合法的 foo_bar 值。
表达式 `(x : foo :> foo_bar)` 要求编译器检查源类型的每个值是否都对目标类型有效。检查通过后,表达式具有目标类型。如果源类型已知,也可以省略;为了让例子清楚,我会使用显式形式。

这是内置强制转换运算符,不是用户定义的转换。只有实际定义满足子类型关系时才能使用。如果源类型中的 `Foo 带有整数载荷,而目标类型中的 `Foo 没有,转换就会失败;给目标类型加上相应整数载荷后,例子就能重新运行。
这个例子中,编译器错误提到了缺少的 `Bar 标签,这虽然属实,却不是失败的原因。关键问题是整数载荷不兼容。当载荷自身使用兼容的递归类型时,这种关系也可以递归成立。
递归类型与列表
子类型关系可以扩展到递归定义和列表。强制转换是一项类型检查,不会产生运行时转换。
强制转换还可以深入一层:foo 列表可以被视为 foo_bar 列表。不同于遍历并重建列表的传统转换,这个运算符保证不产生运行时影响。编译器检查并改变类型,但不会重新分配或遍历列表。

私有类型别名
私有整数别名支持构造不变量。强制转换允许单向访问底层表示,但不允许任意整数变成私有类型。
另一个应用是私有类型别名。模块可以声明 `type t = private int`,同时用整数实现 t。它可以提供只接受正数的构造函数,使模块控制不变量,同时公开底层表示。
私有类型和 int 是不同的:在需要 int 的地方直接使用 t,或者在需要 t 的地方使用 int,都不能通过类型检查。但强制转换运算符允许将 t 视为 int。它也适用于列表内部,因此 t 列表可以强制转换为 int 列表,而无须运行时处理。

反向强制转换会被拒绝。允许任意 int 变为 t 会绕过正数不变量。模块控制 t 值的构造,而调用方可以通过公开的整数表示查看这些值。
同一个运算符也适用于对象类型。下一段视频中,我会探索强制转换如何与新数据类型上的型变注解相互作用。
同频道的其他文章
1:48:29EN → 简体中文
1:36:37EN → 简体中文
1:05:45EN → 简体中文
1:25:00EN → 简体中文
1:16:29EN → 简体中文
27:39EN → 简体中文