函数式编程为何对 Jane Street 重要

原标题Why Functional Programming Matters at Jane Street

一位 Jane Street 开发者重新审视 John Hughes 的经典论点,并解释为什么表达能力强的静态类型对交易系统最重要。

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

30 秒看懂

一位 Jane Street 开发者比较了高阶函数、纯函数和惰性求值与表达能力强的静态类型在实践中的价值。讲者通过交易消息、映射合并和连接状态示例,展示类型如何使代码更清晰,并帮助防止无效状态。

  1. 1Hughes 提出的三个特性讲者重新审视高阶函数、纯函数和惰性求值,认为它们是实现模块化和管理复杂度的工具。
  2. 2交易系统的优先事项当小错误可能造成高昂损失,机会或监管要求又迅速变化时,正确性、清晰度、灵活应变能力和性能都很重要。
  3. 3表达能力强的静态类型居首对于 Jane Street 的工作,讲者认为类型比高阶函数、纯函数和惰性求值更重要。
  4. 4让编译器发现遗漏的情况在交易所消息变体中加入成交撤销后,编译器会指出需要更新的持仓处理代码。
  5. 5让非法状态无法表示Left、Right、Both 类型消除了映射合并中不可能出现的情况,连接状态变体则规定每种状态对应的数据。
  6. 6惰性求值与可预测性讲者认为惰性求值在某些情况下有用,但它的性能在波动市场中可能难以预测。

要点建议

  1. 高阶函数让程序员能够定义控制流并组合行为。
  2. 纯函数更容易推理,但真实系统必须容纳副作用。
  3. 交易系统需要正确、清晰、能够快速安全地修改,并具有可预测的性能。
  4. 选项类型和变体类型明确列出可能情况,让编译器发现遗漏。
  5. 通过类型使非法状态无法表示,可以减轻维护不变量的负担。
  6. 惰性求值可以分离关注点,但可能使性能更难预测。
全文

按章节阅读整理后的全文

只看原始文稿
01

重新审视函数式编程

讲者回应 John Hughes 的论文并介绍其中三个特性,讨论实践中哪些特性最重要。

我是一名 Jane Street 开发者,工作日的大部分时间都在用 OCaml 编写函数式代码。这个标题有点设了个陷阱:我喜欢函数式编程,但我想解释它为何重要,以及为什么一些常见理由对我们来说没那么重要。

这场演讲回应 John Hughes 1988 年的论文《Why Functional Programming Matters》。Hughes 为函数式编程提出了有说服力的论证,我推荐大家阅读。我同意它很有价值,但我会将不同的理由放在首位。

Hughes 将高阶函数、纯函数和惰性求值列为标志性的优势。软件工程在很大程度上是通过分离关注点和组合模块来管理复杂度;他认为这些特性是让模块化成为可能的黏合剂。

02

高阶函数与纯函数

一等函数支持自定义控制流。纯函数更容易组合和推理,但真实系统需要副作用。

高阶函数将函数视为一等值:可以把函数存进哈希表、传给另一个函数,或放进引用。例如,OCaml 的 map 函数接收列表和一个函数,将该函数应用于表头,再递归地映射列表尾部。

OCaml 中的高阶 map
幻灯片 1 / 7OCaml 中的高阶 map这个递归函数接收另一个函数,并将其应用于列表中的每个元素。

高阶函数让我们能够定义自己的控制流,不必只依赖语言设计者提供的循环、迭代器或推导式。一个大型函数起初可能用布尔标志选择行为,后来改用变体;随着选择增多,传入函数就能让它交出控制权,待该函数返回后再继续执行。

纯函数的行为只依赖输入,唯一效果是返回结果。它不依赖计数器或一天中的时间,也不写文件或向屏幕打印。纯函数更容易推理,也能像数学函数一样组合。

可变操作可以通过共享状态在程序各部分之间建立通信通道。由于这个通道没有体现在函数的输入和返回值中,代码可能更难推理。尽管如此,真实系统需要副作用,例如与交易所通信,因此在所有作用域都强制保持纯净可能很困难。

03

惰性求值与模块化

惰性求值支持无限结构,并将国际象棋搜索与可行走法的生成分开。Jane Street 较少使用惰性求值。

惰性求值指表达式只在需要时才计算。可以把自然数无限列表定义为 n 后接从 n + 1 开始的数:严格求值会永远运行下去,而 Haskell 这样的惰性语言可以按需使用该列表。

Hughes 用国际象棋示例说明惰性求值如何将终止条件与循环分开。程序的一部分根据棋规定义可能走法的树,另一部分遍历它以寻找最佳走法。遍历时可以把树视为可用,而不必预先生成整棵树;没有惰性求值时,遍历还必须管理所需节点的生成。

我们同意高阶函数非常有用。纯函数在一定程度上有帮助,但可能让大型系统更难编写;Jane Street 相对较少使用惰性求值。我们的优先顺序反映了所构建的系统类型。

04

交易系统需要什么

公司的系统必须正确、清晰、容易安全地修改,并具有合理性能。除了速度,可预测性也很重要。

Jane Street 是一家自营交易公司和做市商,用自己的资金在全球交易所交易。演讲时,我们每天完成约 100 万笔交易,覆盖约 200 家交易所,并拥有约 1 PB 的历史数据。我们的系统使用 OCaml 编写。

我们首先关心正确性。系统处理数十亿美元,一个高频循环中的小错误可能在几秒内造成巨大损失。由于交易的是自有资金,重大损失会直接影响公司。清晰度也很重要:代码碰巧正确,但晦涩到没人能确定其正确原因,几乎和错误代码一样令人担忧。

我们也重视灵活应变能力,即快速、高效、安全地修改系统的能力。交易机会可能很快消失,监管变化也可能规定期限,过期后我们就不能交易。每位开发者负责许多系统,因此我们希望避免样板代码,并获得语言支持,防止意外后果。

性能同样重要,尽管我们的主要目标不是在降低交易所延迟的竞赛中获胜。交易代码必须有竞争力,而对 1 PB 数据的分析不能无限期运行。OCaml 不如 C 快,但性能合理且相对可预测。

OCaml 编译器相对简单,有助于我们理解机器层面执行的代码;源码的小改动通常不会导致巨大的性能变化。相比之下,我曾见过 Haskell 中细微的改动阻止编译器优化,造成审查时可能不明显的性能变化。这是一个权衡,并非因为性能就应拒绝函数式语言。

05

静态类型与空值安全

表达能力强的静态类型位列讲者的优先事项之首。选项类型明确表示值可能不存在,并要求处理这种情况。

我们的排序是:表达能力强的静态类型第一,高阶函数第二,其次是纯函数,惰性求值则低得多。表达能力强的类型系统可以表示元组、记录和代数数据类型;静态检查让编译器在程序运行前检查是否安全使用它们。

以空指针为例。程序员可能忽略值为空的情况,从而触发异常。Tony Hoare 曾将发明空引用称为自己的“十亿美元错误”。在 OCaml 中,类型为 t 的值就是 t,不会暗中可能为 null;选项类型则明确表示 Some 值或 None。

使用选项时,编译器要求我们考虑两种可能。在类型表达能力不足的语言中,注释可能保证函数绝不返回 null,但注释需要人们信任和维护。类型签名让这种可能性可见,并让编译器强制处理。

06

扩展交易所消息

交易所消息变体对确认、订单退出和成交建模。增加成交撤销后,编译器可以指出需要补充新分支的代码。

类型还帮助我们安全扩展系统。设想交易所发来的执行消息:确认表示已收到订单消息,订单退出表示订单不再有效,成交则报告带有数量和方向的一笔交易。持仓更新函数匹配这些情况:确认和退出不改变持仓,成交则改变持仓。

交易所消息与持仓更新
幻灯片 2 / 7交易所消息与持仓更新变体描述确认、订单退出和成交,模式匹配则更新持仓。

现在假设交易所还发送成交撤销消息,取消此前报告的一笔成交。这类消息出现得足够频繁,我们必须处理。将成交撤销加入变体类型后,编译器会指出未考虑新情况的匹配,包括系统其他位置的代码。我增加功能时会用这种技术找出所有需要重新考虑行为的地方。

向消息类型加入成交撤销
幻灯片 3 / 7向消息类型加入成交撤销新变体表示撤销此前的成交。
07

让非法状态无法表示

映射合并和连接状态的例子展示类型如何消除不可能的组合,使不变量更清晰、更容易维护。

映射合并的函数签名
幻灯片 4 / 7映射合并的函数签名组合函数接收来自两个输入映射的可选值。

第二个例子是合并两个映射。两者使用相同的键类型;提供的函数针对某个键接收分别来自两个映射的可选值,并返回一个可选结果。合并操作考虑任一输入中存在的键,并根据函数结果构建输出映射。

调用者匹配两个可选值时似乎有四种情况,但两者都是 None 的情况不可能发生:该键必定至少出现在一个输入映射中。我们可以断言这种情况不可能,但读者或审查者仍需核实这个说法。

表示一侧或两侧值的类型
幻灯片 5 / 7表示一侧或两侧值的类型Left、Right 和 Both 消除了两侧都不包含该键这一不可能的情况。

另一种做法是定义包含 Left、Right 和 Both 的类型,并将其传给组合函数。这样不可能出现的 None/None 情况就消失了。代码更清晰,审查者不再需要证明该分支不可达,我们也提前剪掉了组合空间中不必要的一支。

更一般的原则是让非法状态无法表示。如果某些值的组合不可能同时存在,就把这一事实编码进类型。也可以手工维护不变量,论证每个函数都保留它,但这很难长期坚持;合适的类型可以从构造上强制保证不变量。

使用可选字段的连接状态
幻灯片 6 / 7使用可选字段的连接状态最初的记录允许字段组合与其状态不相符。

例如,一条连接记录可能包含状态、服务器、ping 信息和时间戳。连接当前处于已连接状态时,断开时间意味着什么?如果连接已断开或从未收到 ping,最近一次 ping 又意味着什么?并非每个字段在每种状态下都有意义。

可以改用变体,让每种状态明确携带其中可用的信息。断开连接的分支包含其他分支没有的断开时间。这样不变量在类型中清晰可见,也能防止意外出现无效组合,从而提升正确性和清晰度。

用变体表示连接状态
幻灯片 7 / 7用变体表示连接状态每种状态只携带与其相关的信息。
08

最终排序

讲者解释惰性求值为何可能使性能复杂化,以及纯函数为何可能增加复杂度。表达能力强的静态类型和高阶函数是最重要的两项。

回顾一下,惰性求值可能有用,但它使性能难以推理。一个多次表现可预测的函数,在陌生且波动的市场环境中可能突然慢得多,而那恰恰是系统需要保持可用以进行交易的时候。

纯函数很有用,OCaml 也支持它;但如果强制使用纯函数引入更多复杂度,对我们来说就不值得。高阶函数很出色,而表达能力强的静态类型位列首位。幸运的是,OCaml 和 Haskell 等语言同时提供这些特性;这个排序描述的是如果必须选择,我们会如何选择。

同频道的其他文章

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