Flow Typing, A New Lens on Linearity
本文为原文前 6,000 字符的节选翻译,完整内容请查看原文。
Title: Flow Typing, A New Lens on Linearity
Papers We Love: Flow Typing, A New Lens on Linearity October 20, 2026 · pl research, programming languages, compiler In my various efforts to develop new programming languages, I’ve occasionally looked sidelong at linear types, as something that looks like it could be really useful if it wasn’t so incredibly cumbersome. And now some folks have come up with a way to make them much easier to use[1]! Or at least that’s the hope. This past week I’ve been trying to implement their execution model into a new programming language, to get a feel for what it’s like to actually use. Check out my field notes, if you’re already familiar with the paper.
“Papers We Love: Flow Typing, A New Lens on Linearity”(我们喜爱的论文:流式类型,线性类型的新视角),2026年10月20日 · 程序语言研究,编程语言,编译器。在我开发新编程语言的各种尝试中,我偶尔会侧目关注线性类型,觉得如果它不是那么极其繁琐的话,应该会非常有用。现在,有些人想出了一种让它们变得更容易使用的方法[1]!至少希望如此。过去一周,我一直在尝试将他们的执行模型实现到一种新的编程语言中,以感受其实际使用体验。如果你已经熟悉这篇论文,可以看看我的现场笔记。
Flow Typing: A New Lens on Linearity I’m going to quote the abstract in full, as it does a great job of introducing the paper: Linear type systems often suffer from a threading problem: if a value is used more than once, then it must be explicitly threaded through each usage. Existing solutions introduce extra language features to mitigate this, but at the cost of complicating the core language and its semantics. This article introduces flow typing, a new solution to the threading problem that avoids the need to extend the core language. Flow typing is based on the theory of lenses, a functional presentation of mutable references, in which lens terms mutate parts of a stateful typing context in a controlled way. Because this approach is purely functional, it can be implemented in a standard linear lambda calculus, providing flow typing with a simple and direct semantics.
“Flow Typing: A New Lens on Linearity”(流式类型:线性类型的新视角)。我将完整引用摘要,因为它很好地介绍了这篇论文:线性类型系统经常受到“线程化问题”的困扰:如果一个值被多次使用,那么它必须显式地穿插(threaded)在每次使用中。现有的解决方案引入了额外的语言特性来缓解这一问题,但代价是使核心语言及其语义变得复杂。本文介绍了流式类型,这是一种解决线程化问题的新方案,它避免了扩展核心语言的需要。流式类型基于透镜(lenses)理论,这是一种可变引用的函数式表达,其中透镜项以受控方式改变有状态类型上下文的部分。由于这种方法是纯函数式的,它可以在标准的线性 lambda 演算中实现,从而为流式类型提供简单直接的语义。
Ok I’ll admit that there are a number of terms in there that likely aren’t totally familiar to the average programmer, and I also want to convince myself that I know what they mean, so let’s break things down a bit. Linear type systems A type system is “linear” if all values must be used exactly once. You might be thinking (a) that sounds incredibly restrictive, I quite like using values multiple times, and (b) why would you even want that. I’ll speak to the second one first. Some reasons a linear type system might be attractive: Mandatory explicit disposal Have you ever forgotten to close a file, and had a dangling file descriptor? Have you ever forgotten to free a pointer? To put it in web dev terms, what about failing to close a database connection, or to unsubscribe a listener?
好吧,我承认其中有很多术语对于普通程序员来说可能并不完全熟悉,而且我也想让自己确信我理解它们的含义,所以让我们把它们拆解一下。线性类型系统:如果所有值都必须恰好使用一次,那么类型系统就是“线性”的。你可能会想:(a)这听起来限制太大了,我很喜欢多次使用值;(b)你为什么要那样做呢?我先回答第二个问题。线性类型系统可能具有吸引力的一些原因:强制显式处置。你是否曾经忘记关闭文件,导致出现悬空的文件描述符?你是否曾经忘记释放指针?用 Web 开发的术语来说,忘记关闭数据库连接或取消订阅监听器又如何呢?
If a value (say a DBConnection) must be used exactly once, and the library offers you (a) functions that take the connection as an argument and then give it back to you in the return value, and (b) a close() function as the only way to “get rid of it”, then you suddently have a statically enforcable mechanism for ensuring you never forget to close a db connection. To make this concrete: let run = () => { let db = sqlite.open(’./my.db’); let [db2, records] = sqlite.run_query(db, RECORD_QUERY); save(records); sqlite.close(db2); }; If the final sqlite.close(db2) line were missing (in a fictional javascript with a linear type system), you’d get a type error db2 must be used exactly once, but was used 0 times. (the save function is a stand-in for “doing something with the records we got from the db”) In such a system, you’re also statically disallowed from ignoring (or forgetting about) a return value.
如果一个值(比如 DBConnection)必须恰好使用一次,并且库为你提供了(a)将连接作为参数并将其在返回值中返回给你的函数,以及(b)作为“摆脱它”的唯一方法的 close() 函数,那么你突然就拥有了一种静态可执行的机制,确保你永远不会忘记关闭数据库连接。具体来说:let run = () => { let db = sqlite.open(’./my.db’); let [db2, records] = sqlite.run_query(db, RECORD_QUERY); save(records); sqlite.close(db2); }; 如果缺少最后的 sqlite.close(db2) 行(在具有线性类型系统的虚构 JavaScript 中),你会得到一个类型错误:db2 必须恰好使用一次,但实际使用了 0 次。(save 函数代表“对从数据库中获取的记录进行处理”)。在这样的系统中,你还被静态禁止忽略(或忘记)返回值。
Purity by obscurity Pure functional languages tend to achieve purity via immutability, to the extent that you’d be forgiven for thinking that it was a necessary component of having a pure language. “Pure” here meaning that “if you give a function the same input, it is always guaranteed to return the same output”[2]. A simple example of an impure function in javascript would be: let lie = false; function addTwo(input) { if (lie) return input + 3; return input + 2; } console.log(addTwo(2)); // 4 lie = true; console.log(addTwo(2)); // 5 This is a rather tame example of action at a distance, an ill that pure functional programming saves you from.
通过隐晦实现纯度。纯函数式语言倾向于通过不可变性来实现纯度,以至于你会认为这是拥有纯语言的必要组成部分。这里的“纯”意味着“如果你给函数相同的输入,它总是保证返回相同的输出”[2]。JavaScript 中不纯函数的一个简单例子是:let lie = false; function addTwo(input) { if (lie) return input + 3; return input + 2; } console.log(addTwo(2)); // 4 lie = true; console.log(addTwo(2)); // 5 这是一个相当温和的“远距离作用”(action at a distance)例子,纯函数式编程可以让你免受这种弊病的困扰。
In Haskell or Elm, the above code is impossible to represent because you can’t “mutate the value that lie represents” after initializing the variable. All variables are immutable, giving you purity by construction. In a linear type system, by contrast, purity is achieved by preventing any variable from having multiple observers. Essentially, “if mutation happens but no-one else can observe it, does it make a sound?”[3] The above code would be disallowed because line 7 is a “second reference to the variable lie”, and therefore a type error.
在 Haskell 或 Elm 中,上述代码无法表示,因为你在初始化变量后无法“改变 lie 所代表的值”。所有变量都是不可变的,从而在构建时就保证了纯度。相比之下,在线性类型系统中,纯度是通过防止任何变量拥有多个观察者来实现的。本质上,“如果发生了变异但没有其他人能观察到它,它会发出声音吗?”[3] 上述代码将被禁止,因为第 7 行是“对变量 lie 的第二次引用”,因此会产生类型错误。
Rust’s linear (ok affine) mutable variables Rust is famous for its linear-inspired setup for mutable variables; if a variable is marked as marked as mutable, there can only be one “accessor” (reader or writer) at a time. This allows them to have some pretty strong guarantees about safety, especially in the context of concurrency. However, they also want to be able to provide access-by-reference for mutable variables (as it would be terribly inconvenient to have to pass-by-value anything that’s mutable), which is accomplished via the infamous “borrow checker”. The linear-style requirements are represented by a system of “borrowing”, where the compiler needs to track which variables are “live” at any given point in the code, and ensure there’s only one live reference to a given value at a time. This is famously confusing and hard for people to learn. In spite of this, Rust is a wildly successful language.
Rust 的线性(好吧,仿射)可变变量。Rust 以其受线性启发的变量可变性设置而闻名;如果一个变量被标记为可变,则同一时间只能有一个“访问者”(读取者或写入者)。这使它们能够在安全性方面提供非常强的保证,特别是在并发环境下。然而,他们也希望能够为可变变量提供引用访问(因为必须按值传递任何可变的东西会非常不方便),这是通过臭名昭著的“借用检查器”(borrow checker)来实现的。线性风格的要求由“借用”系统表示,编译器需要跟踪代码中任何给定点上哪些变量是“活跃的”,并确保同一时间对给定值只有一个活跃引用。这以令人困惑和难以学习而闻名。尽管如此,Rust 仍然是一种非常成功的语言。
The Threading problem Let’s accept for the moment that a linear type system is desirable (or at least interesting), and circle back to the “terribly inconvenient to have to pass-by-value anything that’s mutable” issue. This is what Wilshaw & Hutton call the “threading problem”. I’ll demonstrate with some simple typescript code: let person = new Person(); setName(person, ‘Lucretia’); setAge(person, 51); setSummary(person, getSummary(person)); display(person); We create an object, we mutate it with a couple of functions, and we display it. In typescript, we’re passing by reference, which is how setName etc. are able to affect our object. Passing by reference.
线程化问题。让我们暂时接受线性类型系统是可取的(或者至少是有趣的),并回到“必须按值传递任何可变的东西非常不方便”的问题上。这就是 Wilshaw 和 Hutton 所说的“线程化问题”。我将用一些简单的 TypeScript 代码来演示:let person = new Person(); setName(person, ‘Lucretia’); setAge(person, 51); setSummary(person, getSummary(person)); display(person); 我们创建一个对象,用几个函数改变它,然后显示它。在 TypeScript 中,我们通过引用传递,这就是 setName 等函数能够影响我们对象的方式。通过引用传递。