30 秒看懂
一位 Jane Street 讲者继续介绍公司 OCaml 编译器分支中的局部类型。通过恒等函数和迭代函数,本集解释局部参数的隐藏 regional 处理方式、尾调用以及每次迭代的区域。
- 1看似逃逸的值虽然规则规定局部值不能逃离其区域,恒等函数仍可以接收并返回局部字符串。
- 2隐藏的 regional 模式局部参数由调用者提供,在函数内部是 regional,因此可以逃离一个区域。
- 3丢失 regional 信息将 regional 函数传过恒等函数会返回一个 local 值,该值不能传入尾调用。
- 4循环周围的区域每个循环体区域都可以在下一次迭代之前回收局部分配的内存。
要点建议
- 标记为 local 的参数在函数内部按 regional 处理。
- regional 值来自区域外部,因此可以跨越一个区域边界。
- 返回这样的值时,调用者看到的是 local 值,而不会保留 regional 信息。
- 将 regional 值传过返回 local 值的恒等函数,可能丢失尾调用所需的信息。
- 循环体创建的区域允许每次迭代回收局部分配的内存。
按章节阅读整理后的全文
两个令人意外的例子
局部恒等函数返回其输入,而递归迭代器将局部函数传入尾调用。两者看似都与区域规则冲突。
本集继续介绍 Jane Street 公开提供的 OCaml 编译器分支中的局部值。我将通过看似违反前几集规则的例子,解释区域以及一个名为 regional 的隐藏模式。
考虑一个恒等函数,其参数被标记为局部字符串。它可以通过编译,推断出的类型接收一个局部字符串并返回一个局部字符串。这看起来令人意外:每个函数都定义一个区域,而局部值不应该逃离其区域。

第二个例子是遍历列表的递归迭代器。它接收一个函数,将其应用于每个表头,并对剩余部分进行尾递归调用。把函数参数标记为 local,让调用者可以提供闭包,而不一定要在堆上分配该闭包。

虽然递归调用是尾调用,位于当前函数区域之外,迭代器仍然能够通过编译。为什么函数参数可以传到那里?恒等函数的返回和迭代器的尾调用似乎都允许局部值逃逸。
regional 模式
局部参数在函数内部按 regional 处理,可以跨越一个区域边界。将它传过返回局部值的函数会丢失这一信息。
原因是标记为 local 的参数在函数内部按 regional 处理。regional 是隐藏模式,不是可以写在程序中的关键字。regional 值可以逃离一个区域,但也可以完全不逃离。

参数一定由调用者提供,因此它的生命周期已经超出被调用函数自己的区域。允许它跨越这一个边界是安全的。在恒等函数示例中,返回的字符串对调用者而言是 local:它不能直接成为 global,否则真正的局部值就可能逃得太远。
在迭代器中,函数参数在当前调用内部也是 regional。因此,尽管当前函数的区域在尾调用之前结束,它仍可以跨过该边界,传给下一次调用。
为了展示区别,我将迭代器的函数参数传过一个恒等函数,其类型接收并返回局部值。我们只知道结果是 local;类型没有保留原参数是 regional 的信息。此时将结果传给尾调用就会产生局部值逃逸错误。

循环体中的区域
讲者将迭代器改写为数组循环。每次迭代的区域决定局部分配的内存何时可以回收。
循环体周围也有区域。我将迭代器改写为用 for 循环遍历数组,对每个元素调用函数。这个例子关注循环运行时局部分配的生命周期。
如果被调用函数返回局部值,其局部分配发生在父区域中,本例中就是循环体区域。每次迭代结束时,该区域都可以在下一次迭代之前回收。没有这样的边界,重复工作可能在循环中不断累积空间。

讲者指出,在这个示例中,循环规则对类型检查的影响较小,因为循环通常不会返回值,除非通过引用等机制。但它仍有助于理解内存分配行为:循环区域中的局部分配会随着本次迭代结束而结束。
同频道的其他文章
15:33EN → 简体中文
1:48:29EN → 简体中文
1:36:37EN → 简体中文
1:05:45EN → 简体中文
1:25:00EN → 简体中文
1:16:29EN → 简体中文