使用 OCaml 的 local 模式编程

原标题Programming with OCaml's Local Mode

通过一个例子了解局部列表如何包含全局值、为何仅靠不逃逸标注不能保证栈分配,以及这项实验性功能有哪些不便之处。

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

30 秒看懂

一位 Jane Street 演讲者演示实验性 OCaml 编译器分支中的 local 模式。构造临时 widget 列表的过程揭示了尾调用限制、global 模态、包装类型,以及对局部分配 map 的需求。

  1. 1不能逃逸的列表只有 get_best 承诺不保留中间 widget 列表时,才能将它作为局部值传入。
  2. 2尾调用限制局部实参不能用于该尾调用,因此示例选择关闭尾调用优化。
  3. 3局部列表中的全局元素非装箱包装类型中的 global 模态字段,使元素即使处于局部列表中也保持全局状态。
  4. 4local 不等于栈分配即使结果被视为局部值,普通 List.map 仍会在堆上分配。
  5. 5在调用方区域分配返回局部值的 map 在尾位置使用 exclave,在调用方区域内分配结果。

要点建议

  1. local 参数承诺其实参不会逃逸,因此有时需要避免尾调用。
  2. 通过带 global 模态字段的包装类型,局部列表可以包含全局元素。
  3. 非装箱单字段包装类型可以表达这种区别,而无需在运行时额外分配包装对象。
  4. local 标注表达的是不逃逸,本身不能保证值分配在栈上。
  5. 返回局部值的 map 可以使用 exclave 在调用方的区域内分配列表单元。
  6. 模式多态和更方便的模态语法是未来的可能改进,并非演示中已经完成的功能。
全文

按章节阅读整理后的全文

只看原始文稿
01

临时 widget 列表

演讲者介绍示例,并尝试将中间列表设为局部值。local 参数和 nontail 调用解决了最初的逃逸错误。

在本系列目前关于 locals 的最后一集中,我想用 Jane Street 的 OCaml 编译器分支中的实验性功能,完成一个小型编程示例。这个例子是刻意构造的,但遇到的错误展示了使用局部值时可能出现的实际问题。

假设有一个用整数索引 widget 的映射,我们想找出与 100 以下素数相关的最佳 widget。素数列表已经准备好,get_best 函数接收 widget 列表并返回最佳元素。为了演示,我提供了一些可以编译、但并不打算运行的占位辅助函数。

初始实现遍历素数,查找每个 widget,将结果收集到列表中,再把列表传给 get_best。它可以编译,但中间列表只在该函数执行期间需要,因此我希望避免在堆上分配它。

临时 widget 列表
幻灯片 1 / 5临时 widget 列表初始函数将素数键映射为 widget,并把列表传给 get_best。

把列表标为 local 会产生逃逸错误:原先的 get_best 签名要求全局列表,并没有承诺不会存储这个实参。将其列表参数标为 local,就向编译器提供了所需的不逃逸承诺。

这又暴露了另一个错误:get_best 处于尾位置,因此不能在该尾调用中传入局部实参。添加 nontail 标注可以保持相关区域存活。在这个非递归例子里,放弃尾调用优化是可以接受的。

尾调用中的局部实参
幻灯片 2 / 5尾调用中的局部实参编译器报告局部值逃逸,因为调用位于尾位置。
02

全局元素与局部结构

从局部列表返回元素暴露了局部性问题。非装箱包装类型中的 global 模态字段可以保留元素的全局性。

占位签名现在接收局部列表并返回全局 widget,但我们还没有实现它。真正实现一个取第一个元素的函数后,就出现了下一个问题:从局部列表中取出的值会被视为局部值,因此以全局值形式返回会使它逃逸。

我把 get_best 的输入标为 local 后,它推断出的结果也变成局部值。然而,调用方希望返回原始表中的全局 widget。我们需要区分的是:列表单元是局部的,但元素是全局的。

演示中的解决方案是定义一个包装类型,并在其唯一字段上使用 global 模态。包装类型标为 unboxed,这是 OCaml 针对此类单字段记录提供的通用功能:包装值与包装类型具有相同的运行时表示,因此包装本身不需要额外分配。

global 模态表示,即使外层包装值是局部的,该字段仍是全局的。因此,这些包装值组成的局部列表可以包含全局元素。通过模式匹配提取字段后,get_best 就能全局返回该字段,同时不让局部列表本身逃逸。

带 global 模态的包装类型
幻灯片 3 / 5带 global 模态的包装类型非装箱单字段记录让局部列表可以保存已知为全局的值。
03

不逃逸与内存分配

包装值后,接口恢复为所需形式,但仅靠 local 标注不能改变 List.map 的分配方式。返回局部值的 map 使用 exclave。

最初推断出的输入类型要求一个包装后 widget 的映射,这与原始输入不同。在查找后包装每个 widget,就能修正接口:输入仍是普通 widget 的映射,结果是全局值,中间列表是局部的。

包装查找得到的 widget
幻灯片 4 / 5包装查找得到的 widgetmap 对每个查找到的 widget 进行包装,使公开输入仍为普通 widget 的映射。

这里还有一个微妙之处。local 标注表示列表不会逃逸,但它本身不能保证栈分配。普通 List.map 在堆上分配结果,而全局值可以被视为局部值。因此代码可能通过类型检查,却没有实现期望的分配行为。

为了真正让中间列表在局部区域分配,我另写了一个递归 map。在完整的 Jane Street 环境中,库函数可以提供这一功能,但本例使用标准库环境,因此自行定义一个版本。

map 的类型必须说明结果是局部的。列表的 cons 单元需要分配在调用方的区域内,所以实现会在尾位置使用 exclave。完成这一修改后,map 返回局部分配的列表,调用方无需显式标注也能推断出它的局部性。

返回局部值的 map
幻灯片 5 / 5返回局部值的 map自定义 map 使用 exclave 在调用方区域内构造结果。
04

实践中的不便

包装元素并维护单独的局部 map 增加了代码冗长程度。演讲者讨论未来可能的改进,同时强调该功能仍处于实验阶段。

这个例子暴露了两个不便之处:要在局部列表中保留全局元素,需要包装再解包;为了控制分配,普通 map 也需要一个返回局部值的对应版本。团队希望模式多态可以改善这一点,也在探索内联模态的可能语法,但这项工作仍处于实验阶段。

同频道的其他文章

6 张幻灯片15:33EN → 简体中文
JSJane Street· 2026年10月26日Figgie 策略:解读手牌、争夺多数与定价Bradley, Mike · Jane StreetJane Street 交易员 Bradley 和 Mike 分析三局游戏,展示私人信息、市场竞争和时机如何影响牌价。Decision MakingMathematicsFintech
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