OCaml 如何推断局部性

原标题How OCaml Infers Locality

了解 local 与 global 模式推断、模块签名、整数的模式跨越,以及函数类型之间的转换。

频道
Jane Street
发布
2024年2月7日
视频
22:27
阅读
6 分钟
幻灯片
3
原文
英语
在 YouTube 打开
往下滚动阅读时,视频会缩到右下角继续播放。字幕跟随阅读语言。
阅读语言共 2 种语言可读
由 AI 从英语原文翻译。打开“对照原文”可以逐段核对。
摘要

30 秒看懂

在这期 OCaml Unboxed 演示中,讲者展示编译器如何推断值、参数和返回结果的局部性。例子说明了模块签名如何处理不逃逸的承诺、立即数整数为什么可以跨越模式,以及函数转换有哪些限制。

  1. 1查看带类型的语法树调试标志 -dtypedtree 会显示分配模式,包括尚未确定的模式变量。
  2. 2优先采用宽松的接口local 参数接受 local 和 global 值;global 返回值给调用者更大的自由。
  3. 3整数可以跨越模式立即数 int 没有需要保护的指针生命周期,因此可以用于 global 上下文。
  4. 4安全转换与 eta 展开演示中一种安全的函数转换可能被拒绝,但可以通过 eta 展开来表达。

要点建议

  1. 编译器引入模式变量,在没有约束要求全局分配时优先选择局部分配。
  2. local 参数对调用者更宽松,而 global 返回值通常更有用。
  3. 模块签名可以舍弃 local 参数的承诺,也可以对返回值施加 local 限制。
  4. 立即数整数可以跨越模式,但有时需要显式类型注解。
  5. 某些安全的函数类型转换可能需要 eta 展开,而不安全的承诺不能事后添加。
全文

按章节阅读整理后的全文

只看原始文稿
01

推断分配模式

编译器引入模式变量并根据约束判断值是 local 还是 global。带类型的语法树输出会显示推断出的分配模式。

此前我们讨论了 local 参数、变量和返回值,以及 local 和 global 是模式这一概念。现在来看推断:OCaml 编译器如何确定值和函数的局部性。

假设我在函数内部绑定一个未使用的 x。普通的编译器输出告诉我 f 的类型是 unit 到 unit,却没有说明 x 是 local 还是 global。虽然在这个小例子中没有区别,但随着程序变大,这一区别可能变得重要。

为了演示,我可以传入 -dtypedtree,打印编译器带类型的中间表示。这种输出供编译器调试使用,而非日常编程,格式也不太友好。在 x 旁边,分配模式显示为 <modevar>:编译器尚不知道答案,因此引入了一个模式变量。

检查推断出的分配模式
幻灯片 1 / 3检查推断出的分配模式带类型的语法树显示局部绑定尚未确定的模式变量。

如果我返回 x,它就会逃出函数的区域,打印出的分配模式变为 global。反过来,如果我将 x 标注为 local 并阻止它逃逸,输出就会显示 local。没有这两种约束时,编译器会在类型检查完成后作出选择,并优先采用局部分配。

局部性提供了安全属性,但 Jane Street 开展这项工作的动机也涉及分配:OCaml 程序的某些部分可以采用栈式分配,而不必在需要垃圾回收的堆上分配。从这个意义上说,局部分配可能更好。后续视频中我会进一步讨论它的分配行为。

02

参数与模块签名

local 参数既接受 local 实参,也接受 global 实参。模块匹配可以舍弃实现所作的不逃逸承诺。

更一般地说,推断会尽可能保持宽松。对于函数参数,local 更宽松:global 是 local 的子模式,因此接受 local 实参的函数也能接受 global 实参。忽略参数的函数是个简单例子,尽管实用函数还可以做更多事情。

普通输出中打印的这个函数类型并不显示 local 参数。为了弄清发生了什么,我将它放入一个模块,并给模块添加签名。普通参数类型和 local_ 参数类型都能匹配同一个实现。

模块签名不会用于检查结构本身的类型。编译器先推断结构,再检查结果是否匹配签名。在这个例子中,它为 f 推断出 local 参数,因此该实现可以满足要求 local 参数的签名。

没有签名时,例如模块没有 mli 文件,一个默认化步骤会从显示的模块类型中移除 local_ 标记。这避免了没有接口文件的项目出现令人意外的结果,尽管实现还有改进空间。推断出的实现仍然可以支持 local 实参。

函数并不一定只能忽略参数。它可以把 x 传给另一个承诺不让它逃逸的函数,或者以其他方式操作它。构造例子比较困难,因为标准库的大部分内容尚不支持 local,调用这些函数可能迫使值成为 global。

为什么推断为 local 的参数也能匹配没有 local_ 的签名?local 参数是实现作出的一项承诺:不让实参逃逸。模块签名匹配允许我们舍弃这一承诺,推断出的模块类型与要求较少的签名兼容。

03

返回值与逃逸

global 返回值给调用者更大的自由。参数和返回值上的局部性分别对调用的不同一方施加义务。

对于返回值,偏好则相反。global 结果更有用,因为调用者可能需要在 global 上下文中使用它。如果函数返回 local 字符串,我不能直接将结果传给要求 global 字符串的函数;local 值不会自动变成 global。

Jane Street 内部有一个 globalize PPX,可以在需要 global 值时转换 local 值,但这个例子中没有它。返回值上的 local 注解对调用者施加限制:调用者不能让结果逃逸。在签名中加入这一限制是安全的。

对于参数,local 则对函数实现提出要求。如果 h 存储实参或通过其他方式让实参逃逸,推断出的参数就是 global,要求 local 参数的签名就会失败。我们可以从签名的参数中移除 local,或者给返回值添加 local,但一般不能反过来做。

04

立即数整数与模式跨越

整数是立即数,因此可以跨局部性模式使用。显式类型注解可能影响推断结果。

整数是一个重要例外。考虑一个函数,它将 x 与自身相加,然后将 x 存入引用。local 的 x 通常会因为逃逸而被拒绝,但如果将 x 显式标注为 int,同样的代码就会被接受。

将局部整数存入引用
幻灯片 2 / 3将局部整数存入引用显式的 int 注解使该例子通过模式跨越完成类型检查。

OCaml 的 int 是立即数:它不通过指针表示。local 的设计围绕分配行为展开,因此立即数不需要遵循相同的指针生命周期规则。我们说 int 可以跨越模式:即使是 local int,也可以用在要求 global 值的地方;换句话说,int 始终可以视为 global。

这里的类型注解很重要。如果编译器只是从加法推断出 x 是 int,这个模式跨越的例子不会通过。让信息以这种方式跨表达式流动可能使类型检查变得脆弱,因此程序员必须在某处声明 x 是 int。

这也是我在前面的例子中使用字符串的原因:字符串不能跨越模式。这也解释了为什么 local int 能传给加法,即使标准库通常不了解 local。int 在调用时跨越了模式。

这带来一个令人不满意的限制:如果我希望 int 具有 local 的额外安全属性,模式跨越会阻止我获得它。我们正在开发更细粒度的控制,以决定哪些值可以跨越模式。指针表示的值这样跨越是不安全的,但即使是立即数,有时也可能需要限制。

05

函数类型之间的转换

有些转换可以安全地增加或舍弃承诺,另一些则会施加无法保证的要求。讲者展示了用 eta 展开绕过编译器拒绝的安全转换。

推断还允许不同局部性函数之间的一些转换。如果 f 返回 global 字符串,我可以在要求返回 local 字符串的函数的位置使用它:后者只是对调用者如何处理结果增加了一项限制。

反方向的承诺则不同。我不能把接受 global 参数的函数拿来,事后声称它接受 local 参数:这会对实现添加一项它并未承诺满足的要求。

被拒绝的函数转换
幻灯片 3 / 3被拒绝的函数转换将带 local 参数的函数直接赋给普通函数类型时,编译器报告不匹配。

有意思的是,演示中编译器也拒绝将一个带 local 参数的函数赋给普通函数类型,尽管舍弃这个承诺是安全的。这个限制到你观看时可能已经修复。通过 eta 展开,也就是用 fun x -> h x 代替 h,可以绕过这次拒绝。

eta 展开无法让不安全的转换成立。如果 f 从未承诺接受 local 实参,用另一个函数包装它也无法创造这一承诺。不同局部性的函数类型之间,有些转换是安全且受支持的,但并非所有转换都安全。

这带来的好处是,你通常不需要在每个项或变量上写 local 注解。和普通类型推断一样,局部性推断通常能算出所需模式,不过也有我们今天探讨的注意事项。

同频道的其他文章

1:48:29EN → 简体中文
JSJane Street· 2026年10月16日AI 如何改变编程:John Krazit 与 Ron Minsky 的对话Ron Minsky, John Krazit · Jane StreetJane Street 的 AI 工具负责人和 Signals and Threads 主持人探讨智能体基础设施、安全、代码审查,以及软件开发体验的变化。编程智能体Developer productivity评估+2
1:36:37EN → 简体中文
JSJane Street· 2026年9月23日Aaron Bauer:在 AI 时代教授软件工程Ron Minsky, Aaron Bauer · Jane Street当模型能够生成代码时,学习目标、主动练习和编辑器设计如何影响工程教育。AI in EducationSoftware engineeringEducation+2
9 张幻灯片1:05:45EN → 简体中文
JSJane Street· 2026年9月11日Cyrus Omar:实时编程与人与 AI 共享的计算公共空间Cyrus Omar通过 Hazel 演示,探讨即时反馈、结构化协作和精细权限如何帮助人类与 AI 智能体共同工作。Programming LanguagesHuman-AI Collaboration开发流程与测试+2
1:25:00EN → 简体中文
JSJane Street· 2026年9月2日Eric Mannes:如何将杂乱的外部数据用于交易研究Ron, Eric Mannes · Jane Street从商品期货和 Excel 风险系统,到时点数据集和 AI,Eric Mannes 解释 Jane Street 数据工程背后的领域知识。Data quality & curation开发流程与测试Investing+2
7 张幻灯片1:16:29EN → 简体中文
JSJane Street· 2026年8月26日Grant Sanderson 与 Alok Puranik:从群论看位置编码Grant Sanderson, Alok Puranik · 3Blue1Brown从注意力机制的假设出发,他们推导矩阵指数,并将旋转、衰减和 Jordan 块与 RoPE、ALiBi 联系起来。Transformers & attentionMathematics机器学习基础+1
27:39EN → 简体中文
JSJane Street· 2026年8月21日3Blue1Brown 与 Jane Street 谈交易、机器学习和文化Grant Sanderson, Nitya, In Young, Craig, Alok · 3Blue1BrownGrant Sanderson 与 Jane Street 的同事讨论做市、共享算力、研究,以及他们加入公司的不同经历。Machine LearningFintechCompany Culture+2