30 秒でわかる
A Jane Street developer compares the practical value of higher-order functions, purity and laziness with expressive static types. Using trading messages, map merging and connection states, the speaker shows how types can make code clearer and help prevent invalid states.
- 1Hughes’s three featuresThe speaker revisits higher-order functions, purity and laziness as tools for modularity and managing complexity.
- 2Trading-system prioritiesCorrectness, clarity, dexterity and performance matter when small errors can be costly and opportunities or regulations change quickly.
- 3Expressive static types come firstThe speaker ranks types above higher-order functions, purity and laziness for Jane Street’s work.
- 4Let the compiler find missing casesAdding a bust to an exchange-message variant exposes position-handling code that must be updated.
- 5Make illegal states unrepresentableA Left/Right/Both type eliminates an impossible map-merge case, and connection variants encode which data belongs to each state.
- 6Laziness and predictabilityThe speaker values laziness in some cases but says its performance can be difficult to predict in volatile markets.
ポイント
- Higher-order functions allow programmers to define control flow and compose behaviour.
- Pure functions are easier to reason about, but real systems must accommodate side effects.
- Trading systems need correctness, clarity, fast safe changes and predictable performance.
- Option and variant types make possible cases explicit and let the compiler expose omissions.
- Designing types so illegal states cannot be represented reduces the burden of maintaining invariants.
- Laziness can separate concerns but may make performance harder to predict.
チャプターごとに整えた全文を読む
Revisiting functional programming
The speaker responds to John Hughes’s paper and introduces its three features. The talk asks which features matter most in practice.
I’m a Jane Street developer and spend most of my day writing functional code in OCaml. The title is a trick: I like functional programming, but I want to explain both why it matters and why some of its familiar justifications matter less to us.
This talk responds to John Hughes’s 1988 paper, Why Functional Programming Matters. Hughes makes a compelling case for functional programming, and I recommend reading it. I agree that it is valuable, but I would put different reasons at the top of the list.
Hughes identifies higher-order functions, purity and laziness as characteristic strengths. Software engineering is largely about managing complexity by separating concerns and composing modules; he presents these features as glue that makes modularity possible.
Higher-order functions and purity
First-class functions enable custom control flow. Purity makes composition and reasoning easier, though real systems require side effects.
A higher-order function treats functions as first-class values: they can be stored in a hash table, passed to another function or put in a reference. An OCaml map function, for example, takes a list and a function, applies the function to each head and recursively maps the tail.

Higher-order functions let us define our own control flow instead of relying only on mechanisms provided by the language designer, such as loops, iterators or comprehensions. A large function might initially select behaviour with a Boolean flag, then a variant; as choices multiply, passing in a function lets it hand off control and resume when that function returns.
Purity means a function’s behaviour depends only on its inputs and its only effect is to return its result. It does not depend on a counter or the time of day, write a file or print to the screen. Pure functions are easier to reason about and compose much like mathematical functions.
Mutation can create a communication channel between parts of a program through shared state. Because that channel is not apparent in a function’s inputs and return value, it can make code hard to reason about. Nevertheless, real systems need side effects, such as communicating with an exchange, so enforcing purity at every scope can be difficult.
Laziness and modularity
Lazy evaluation enables infinite structures and separates the chess search from generation of possible moves. Jane Street uses laziness less often.
Laziness means an expression is evaluated only when needed. An infinite list of natural numbers can be defined by putting n before the numbers starting at n + 1: a strict evaluation would run forever, while a lazy language such as Haskell can consume the list as needed.
Hughes uses a chess example to show how laziness separates termination conditions from loops. One part of a program can define a tree of possible moves from the rules of chess, while another traverses it to find an optimal move. The traversal can treat the tree as available without generating the whole thing in advance; without laziness, it must also manage generating the nodes it needs.
We agree that higher-order functions are extremely useful. Purity helps to a point but can make large systems harder to write, and Jane Street reaches for laziness relatively rarely. Our priorities reflect the systems we build.
What trading systems need
The firm’s systems must be correct and clear, easy to change safely, and reasonably performant. Predictability matters alongside speed.
Jane Street is a proprietary trading firm and market maker, trading its own money on exchanges around the world. At the time of this talk, we make roughly a million trades a day across about 200 exchanges and have about a petabyte of historical data. Our systems are written in OCaml.
We care first about correctness. Our systems handle billions of dollars, and a small bug in a hot loop can become very expensive in seconds. Because we trade our own money, a serious loss directly affects the firm. Clarity matters too: code that happens to be correct but is so obscure that nobody can establish why is almost as troubling as incorrect code.
We also value dexterity: the ability to change systems quickly, efficiently and safely. Trading opportunities can disappear quickly, and regulatory changes may come with deadlines after which we cannot trade. With many systems per developer, we want to avoid boilerplate and have language support that helps prevent unintended consequences.
Performance matters as well, though we are not primarily trying to win a race to minimise exchange latency. Trading code must be competitive, and analyses over a petabyte of data cannot take forever. OCaml is not as fast as C, but it offers reasonable and relatively predictable performance.
The relative simplicity of OCaml’s compiler helps us understand what code executes at the machine level; small source changes tend not to cause large performance changes. By contrast, I have seen subtle changes in Haskell prevent a compiler optimisation and produce a performance change that might not be apparent in review. This is a trade-off, not a reason to reject functional languages on performance grounds.
Static types and null safety
Expressive static types lead the speaker’s list. Option types make the absence of a value explicit and require it to be handled.
Our ranking puts expressive static types first, higher-order functions next, then purity, with laziness much lower. An expressive type system can represent tuples, records and algebraic data types; static checking lets the compiler check their safe use before the program runs.
Consider null pointers. A programmer can overlook a case where a value is null and trigger an exception. Tony Hoare called the invention of the null reference his billion-dollar mistake. In OCaml, a value of type t is a t, rather than a t that may silently be null; an option type explicitly represents Some value or None.
When an option is used, the compiler requires us to consider both possibilities. In languages without such expressive types, a comment might promise that a function never returns null, but a comment must be trusted and maintained. A type signature makes the possibility visible and lets the compiler enforce handling it.
Extending exchange messages
An exchange-message variant models acknowledgements, outs and fills. Adding a bust lets the compiler flag code that needs a new case.
Types also help us extend systems safely. Imagine an execution message from an exchange: an acknowledgement says an order message was received, an out says an order is no longer live, and a fill reports a trade with a size and direction. A position-update function matches these cases: acknowledgements and outs leave the position unchanged, while fills change it.

Now suppose the exchange also sends a bust, cancelling a previously reported fill. These messages occur regularly enough that we must handle them. When we add a bust to the variant type, the compiler identifies matches that have not considered the new case, including code elsewhere in the system. I use this technique when adding features to find all the places whose behaviour needs reconsideration.

Making illegal states unrepresentable
Map merging and connection state examples show how types eliminate impossible combinations. This makes invariants clearer and easier to maintain.

A second example is merging two maps. Both have the same key type, and a supplied function receives an optional value from each map for a key and returns an optional result. The merge considers keys present in either input and builds the resulting map from the function’s results.
A caller matching the two optional values appears to have four cases, but the case where both are None cannot occur: the key must have appeared in at least one input map. One could assert that this case is impossible, but a reader or reviewer still has to verify the claim.

Instead, define a type with Left, Right and Both cases and pass that to the combining function. The impossible None/None case disappears. The code becomes clearer, reviewers no longer have to prove that branch unreachable, and we prune an unnecessary branch of the combinatorial space early.
The larger principle is to make illegal states unrepresentable. If certain combinations of values cannot coexist, encode that fact in the types. You can maintain an invariant by hand, arguing that every function preserves it, but that is hard to sustain; a suitable type can enforce it by construction.

For example, a connection record might contain a state, server, ping information and timestamps. What does a disconnection time mean when the connection is currently connected? What does a last ping mean if it is disconnected or has never received one? Not every field is meaningful in every state.
Instead, use variants that explicitly carry the information available in each state. The disconnected case contains a disconnection time that the other cases do not. This makes the invariant visible in the type and prevents accidental invalid combinations, improving both correctness and clarity.

The final ranking
The speaker explains why lazy evaluation can complicate performance and purity can add complexity. Expressive static types and higher-order functions are the highest priorities.
To recap, laziness can be useful but makes performance difficult to reason about. A function that has behaved predictably many times may suddenly be much slower in an unfamiliar, volatile market situation—precisely when the system needs to stay available for trading.
Purity is useful, and OCaml supports it, but if enforcing it introduces more complexity, the trade is not worthwhile for us. Higher-order functions are excellent, while expressive static types sit at the top of our list. Fortunately, languages such as OCaml and Haskell provide these features together; the ranking describes what we would choose if we had to choose.
同じチャンネルの記事
1:48:29EN
1:36:37EN
1:05:45EN
1:25:00EN
1:16:29EN
27:39EN