Programming with OCaml's Local Mode

An example shows how a local list can contain global values, why a non-escaping annotation alone does not ensure stack allocation, and where the experimental feature remains awkward.

Canal
Jane Street
Publicado
24 jun 2024
Vídeo
14:36
Lectura
3 min
Diapositivas
5
Original
inglés
Abrir en YouTube
Al desplazarte, el vídeo se reduce a la esquina inferior derecha y sigue reproduciéndose. Los subtítulos siguen tu idioma de lectura.
Idioma de lecturaDisponible en 2 idiomas
Este artículo aún no está en español, así que se muestra el original en inglés.
RESUMEN

En 30 segundos

A Jane Street speaker demonstrates local mode in an experimental OCaml compiler branch. Building a temporary list of widgets exposes tail-call restrictions, global modality, wrapper types and the need for a local-allocating map.

  1. 1A list that must not escapeThe intermediate widget list can be passed to get_best only when the function promises not to retain it.
  2. 2Tail-call restrictionThe local argument cannot be used in that tail call, so the example opts out of tail-call optimization.
  3. 3Global elements inside a local listA globally modal field in an unboxed wrapper preserves the elements' global status even when the list is local.
  4. 4Local does not mean stack allocatedOrdinary List.map still allocates on the heap even if its result is treated as local.
  5. 5Allocate in the caller's regionA local-producing map uses exclave in tail position to allocate its result in the caller's region.

Conclusiones

  1. A local parameter promises that its argument will not escape, which may require avoiding a tail call.
  2. A local list can contain global elements using a wrapper field with global modality.
  3. An unboxed single-field wrapper can express this distinction without adding a runtime wrapper allocation.
  4. A local annotation expresses non-escape; it does not by itself ensure that a value is allocated on the stack.
  5. A local-producing map can allocate list cells in the caller's region using exclave.
  6. Mode polymorphism and more convenient modality syntax were future possibilities, not completed features in this demonstration.
TEXTO COMPLETO

Lee el texto editado por capítulos

Solo la transcripción
01

A temporary widget list

The speaker introduces the example and tries to make its intermediate list local. A local parameter and a nontail call resolve initial escape errors.

In this final installment of the current series on locals, I want to work through a small programming example using the experimental feature in Jane Street's OCaml compiler branch. The example is deliberately contrived, but the errors we encounter illustrate practical issues that can arise when working with local values.

Imagine a map of widgets indexed by integers. We want the best widget associated with a prime below 100. A list of primes is already available, and a get_best function takes a widget list and returns the best element. For the demonstration, I supply placeholder helpers that compile but are not intended to run.

The initial implementation maps over the primes, looks up each widget, collects the results in a list, and passes that list to get_best. It compiles. But the intermediate list is only needed during this function, so I would prefer not to allocate it on the heap.

The temporary widget list
DIAPOSITIVA 1 / 5The temporary widget listThe initial function maps prime keys to widgets and passes the list to get_best.

Annotating the list as local produces an escape error: the original get_best signature expects a global list and makes no promise that it will not store the argument. Marking its list parameter local gives the compiler the needed non-escape promise.

That exposes another error: get_best is in tail position, so the local argument cannot be passed in that tail call. Adding a nontail annotation keeps the relevant region alive. In this nonrecursive example, sacrificing tail-call optimization is acceptable.

A local argument in a tail call
DIAPOSITIVA 2 / 5A local argument in a tail callThe compiler reports that the local value escapes because the call is in tail position.
02

Global elements and local structure

Returning an element from a local list reveals a locality problem. A globally modal field in an unboxed wrapper preserves global elements.

The placeholder signature now accepts a local list and returns a global widget, but we have not implemented it. A real implementation that takes the first element reveals the next problem: a value extracted from a local list is treated as local, so returning it globally would let it escape.

After I mark get_best's input local, its inferred result is also local. The caller wants to return a global widget from the original table, however. The distinction we need is a list whose cells are local but whose elements are global.

The solution demonstrated is a wrapper type with a global modality on its single field. The wrapper is marked unboxed, a general OCaml feature for this kind of single-field record: the wrapped value and wrapper have the same runtime representation, so the wrapper itself does not require an extra allocation.

The global modality says that the field remains global even when the enclosing wrapper is local. A local list of these wrappers can therefore contain global elements. Pattern matching extracts the field, allowing get_best to return it globally without letting the local list itself escape.

A globally modal wrapper
DIAPOSITIVA 3 / 5A globally modal wrapperAn unboxed single-field record lets a local list hold values known to be global.
03

Non-escape versus allocation

Wrapping values restores the desired interface, but a local annotation alone does not change how List.map allocates. A local-producing map uses exclave.

The inferred input type initially expects a map of wrapped widgets, which is not what we started with. Wrapping each widget after the lookup fixes the interface: the input remains a map of ordinary widgets, the result is global, and the intermediate list is local.

Wrapping the widget lookup
DIAPOSITIVA 4 / 5Wrapping the widget lookupThe map wraps each retrieved widget so the public input remains a map of ordinary widgets.

There is still a subtlety. A local annotation says the list does not escape; it does not by itself guarantee stack allocation. Ordinary List.map allocates its result on the heap, and a global value can be treated as local. The code may type-check while failing to achieve the desired allocation behavior.

To actually allocate the intermediate list locally, I write a separate recursive map. In a full Jane Street environment, library functions can provide this functionality, but the example uses the standard library context and defines its own version.

The map's type must say its result is local. Its cons cells need to be allocated in the caller's region, so the implementation uses exclave in a tail position. With that change, the map returns a locally allocated list and the caller can infer its locality without an explicit annotation.

A local-producing map
DIAPOSITIVA 5 / 5A local-producing mapThe custom map uses exclave to construct its result in the caller’s region.
04

Practical rough edges

Wrapping elements and maintaining a separate local map introduce verbosity. The speaker discusses possible future improvements while stressing that the feature is experimental.

The example exposes two sharp corners: preserving global elements in a local list requires wrapping and unpacking them, and ordinary map needs a local-producing counterpart to control allocation. The team hopes mode polymorphism may improve this and is exploring possible syntax for inline modalities, but the work remains experimental.

Más de este canal

6 diapositivas15:33EN
JSJane Street· 26 oct 2026Figgie Strategy: Reading Hands, Winning Majorities and Pricing CardsBradley, Mike · Jane StreetJane Street traders Bradley and Mike analyze three games to show how private information, market competition and timing affect card prices.Decision MakingMathematicsFintech
1:48:29EN
JSJane Street· 16 oct 2026How AI Is Changing Programming: John Krazit and Ron MinskyRon Minsky, John Krazit · Jane StreetJane Street’s AI tooling lead and the Signals and Threads host examine agent infrastructure, security, code review and the changing experience of writing software.Agentes de programaciónDeveloper productivityEvaluaciones+2
1:36:37EN
JSJane Street· 23 sept 2026Aaron Bauer on Teaching Software Engineering in the Age of AIRon Minsky, Aaron Bauer · Jane StreetHow learning goals, active practice and editor design shape engineering education when models can generate the code.AI in EducationSoftware engineeringEducation+2
9 diapositivas1:05:45EN
JSJane Street· 11 sept 2026Cyrus Omar on Live Programming and a Computational Commons for Humans and AICyrus OmarA demonstration of Hazel shows how immediate feedback, structured collaboration and carefully scoped permissions could support people and AI agents working together.Programming LanguagesHuman-AI CollaborationFlujos de desarrollo y pruebas+2
1:25:00EN
JSJane Street· 2 sept 2026Eric Mannes on Turning Messy External Data Into Trading ResearchRon, Eric Mannes · Jane StreetFrom commodity futures and an Excel risk system to point-in-time datasets and AI, Eric Mannes explains the domain knowledge behind data engineering at Jane Street.Data quality & curationFlujos de desarrollo y pruebasInvesting+2
7 diapositivas1:16:29EN
JSJane Street· 26 ago 2026Grant Sanderson and Alok Puranik: Positional Encodings Through Group TheoryGrant Sanderson, Alok Puranik · 3Blue1BrownStarting with assumptions about attention, they derive matrix exponentials and connect rotations, decay and Jordan blocks to RoPE and ALiBi.Transformers & attentionMathematicsFundamentos de ML+1