En 30 segundos
Cristina Rosu explains how Jane Street’s Incr_dom framework uses OCaml to build interactive web interfaces. A live demonstration shows how incremental maps improve updates to a large table, while the discussion covers rendering, composition, testing and asynchronous actions.
- 1A demanding tableA mock trading interface has 10,000 frequently changing rows and uses partial rendering.
- 2Model, view and actionsThe Elm architecture separates state, presentation and updates.
- 3Diff and patchVirtual DOM comparison produces a small patch for the real browser DOM.
- 4Incremental computationDependency graphs and cutoffs avoid recomputing unchanged results.
- 5A 30,000-row testIncremental maps improve subsequent updates but do not eliminate slow initial loading.
- 6Asynchronous actionsA delayed action may encounter a newer model and should be checked before application.
Conclusiones
- js_of_ocaml lets browser and server code share OCaml libraries and types.
- The Elm architecture separates an application into a model, view and actions.
- Virtual DOM limits browser changes, while Incremental limits recomputation of the virtual view.
- Incremental updates do not remove the cost of constructing a large initial view.
- Partial rendering and careful treatment of asynchronous actions remain important in real applications.
Lee el texto editado por capítulos
The problem: large, dynamic web interfaces
Rosu introduces Jane Street’s requirements and a trading UI with 10,000 rows. Partial rendering reduces the number of displayed DOM elements.
At Jane Street, we designed Incr_dom to make it easier to write internal web interfaces in OCaml. I will first explain its main ideas and then build a small application to show how those ideas fit together.
Much of our engineering work concerns trading systems, yet we frequently need interfaces for large amounts of data that users can inspect and change. We wanted web UIs without requiring every developer to become a web specialist.
A mock trading-system interface illustrates the problem: it has 10,000 rows, changing tens of times a second. Users can move a focus, edit cells, filter symbols and sort by columns; changing data can also change the ordering.
Sorting and filtering must respond when the relevant data changes. To make rendering efficient, the example uses partial rendering: only the rows currently in view are actually rendered, with rows added and removed as the user scrolls. This technique is commonly used with Incr_dom but is not itself part of the library.

Our goals were to make web UIs easy to write, maintain and optimize. We hoped a developer could add a UI to an existing system without learning JavaScript in depth, and still have it remain responsive with large, frequently changing datasets.
OCaml in the browser and the Elm architecture
js_of_ocaml enables browser code to share the language used on the backend. The Elm pattern separates model, view and actions.
Incr_dom draws on existing ideas, notably React and Elm. React is a JavaScript library for interactive UIs, while Elm is a functional language designed for interfaces; the framework combines ideas from them with OCaml tooling.
The first building block is js_of_ocaml, which compiles OCaml bytecode to JavaScript and makes it possible to run OCaml in a browser. This lets our client and server share code, making backend changes easier to carry through and compiler errors easier to catch.
We can also reuse the OCaml libraries and tools we have built over the years, along with the language knowledge our developers already have. Benefits such as type safety and expressiveness apply to web interfaces too.
The Elm architecture supplies a pattern for organizing the application. It resembles model-view-controller: a model represents the application state, a view displays it, and actions describe how the model may change.

The view is produced from the model. Event handlers, such as a button click or text entry, schedule actions; applying an action produces a new model, from which the next view is constructed.
This separation gives each part a clear responsibility. It also makes the view declarative: rather than specifying a sequence of edits to the old screen, the programmer describes what the new view should look like.
An application managed by Incr_dom supplies model and action types plus apply_action and view functions. The framework supplies scheduling and connects the pieces; apply_action takes a model and an action and returns a new model.

Virtual DOM and incremental computation
Virtual DOM reduces expensive browser mutations. Incremental dependency graphs and cutoffs reduce work constructing the view.
The view initially appears to produce an HTML DOM, a mutable tree linked to the browser. Changing the actual DOM can trigger expensive browser work such as recomputing styles and layout and repainting.
Rebuilding the real DOM whenever the model changes would be impractical. Virtual DOM addresses this by keeping a lightweight in-memory representation that can be changed without directly triggering browser work.
In the diff-and-patch example, the old virtual tree has nodes A, B and C; the new tree adds D. Comparing them produces a patch containing that addition, and only the patch is applied to the actual DOM.

Our virtual DOM library exposes attributes and nodes. A node has a tag, attributes and child nodes, closely mirroring HTML; convenience functions such as body make the OCaml syntax shorter.
Returning a virtual DOM instead of rebuilding the real DOM reduces browser work, but recreating the entire virtual tree after every model change can still be too costly. This is where the Incremental library comes in.
Incremental supports self-adjusting computations represented as a dependency graph. When an input changes, its descendants can be recomputed; a cutoff stops propagation when a recomputed node has the same value as before.
In Incr_dom, Incremental makes the conversion from model to virtual DOM more efficient. If only a small part of the model changes, the computation can update just the corresponding part of the virtual view.
An incremental value of type alpha Incr.t represents a graph node whose value has type alpha. Incr.map creates a node from one parent and a function, while map2 combines two parents.
The let%map syntax is a PPX expansion rather than a change in semantics. The incremental value appears on the right, and the ordinary value available to the mapped function appears on the left; and combines several incremental inputs.
For an example, take three integer input nodes and derive their minimum and maximum. Two further nodes turn those values into virtual DOM text, producing a graph that reflects exactly which results depend on which inputs.

In an application, those input nodes can be projected from the incremental model and their views combined into a body node. Since every view node descends from the model, cutoffs matter: if an unrelated model field changes, unchanged projections stop unnecessary downstream recomputation.
Incremental maps
Incr_map updates a transformed key-value map efficiently. It is useful for tables and other repeated elements.

Incr_map builds on Incremental to work with key-value maps. An ordinary map transformation produces a new map; its incremental counterpart takes and returns incremental maps and can update the output in proportion to the input difference.
That is especially useful when displaying rows or cells that might otherwise be represented as lists. The view interface now takes an incremental model and produces an incremental virtual DOM rather than reconstructing everything for every change.
Building a counter app
The demonstration constructs an app with a model, actions, event handlers and a virtual DOM. It then introduces scheduled updates.
The demonstration starts with a page containing only a heading. The Dune file specifies the OCaml executable, dependencies, js_of_ocaml compilation and preprocessors; the HTML includes the generated JavaScript, and CSS supplies basic styling.

The main OCaml file implements the application interface and an initial model. At first both model and action are unit, so the app has no meaningful state or behavior. The model cutoff uses an equality function, here physical equality.
The action module also specifies serialization and which actions to log. A separate State module holds imperative state such as server connections; the model should be immutable so Incremental can behave as expected.
Initially apply_action returns the model unchanged and view makes a simple body node. Mapping over the entire model at the top of view is easy to understand, but everything beneath it is recomputed whenever the model changes.
The app supplies trivial implementations of other required interface functions and starts with Start_app.simple. After building with Dune and refreshing the browser, the updated heading confirms that the compiled code is running.
I replace the unit model with a list of 100 counters, initially random integers from 0 to 99. Mapping over the list creates table rows with an index and a counter value; standard CSS provides borders and alternating row colors.
The counter values can also determine their own styles: values below 10 are green, those below 100 blue, and larger values red. These attributes are computed in ordinary OCaml and can change as the data changes.
To make the table interactive, I define Increment and Reset action variants with an index. apply_action pattern-matches on the action and updates the selected counter, adding one for Increment or replacing its value with zero for Reset.
Event handlers schedule those actions. The view receives inject, which is like schedule but has a type designed for virtual DOM event handlers: one click increments the row and a double click resets it.
The startup function also schedules random increments approximately 20 times a second. In the browser, values change on their own, clicks increment them, and double clicks reset them; for 100 rows the result is reasonably smooth.

Scaling the demonstration
At 30,000 rows, the original app becomes sluggish. Incremental maps improve updates, and an incremental fold maintains a total.
To test scale, I make the row count configurable through a URL query parameter, defaulting to 100. At 30,000 rows the initial load takes several seconds, scrolling becomes choppy and resetting a counter shows visible delay.
An audience member asks whether changing the data structure might help. I agree that it could, and switch from a list to a map so the application can use Incr_map. Merely changing the data structure and corresponding calls, however, has not yet made the view incremental.
The original view mapped over the whole model before constructing rows, so even a small change recomputed the entire body. I remove that mapping and use Incr.Map.mapi on the incremental model, mapping over the resulting rows where a plain value is needed.
After recompiling, the first load remains slow: Incremental helps update an existing computation, not perform its initial work for free. Once loaded, scrolling and clicking or resetting counters are noticeably smoother.
To illustrate another incremental map operation, I add a total of all counters using unordered_fold. The accumulator starts at zero; adding an entry adds its value, and removing an entry subtracts its value.

The total appears above the table and remains responsive as counters change. With ten rows, resetting values makes it easier to see the total decrease as expected.
From demonstration to production
Partial rendering, visibility hooks and shared derived computations address needs of more realistic interfaces. Rosu revisits the framework’s design goals.
A realistic application needs additional techniques. Partial rendering can improve initial load by rendering only visible elements and using an appropriately sized spacer for offscreen content; the correct size prevents unexpected jumps.
Keeping a focused row in view uses update_visibility and on_display. The former is called after DOM changes, window resizing or scrolling and can measure the focus relative to the viewport; the latter can compare the current and previous models.
That comparison lets the app scroll the focus into view when the user moves it, without dragging the user back when they have intentionally scrolled elsewhere, for example to type in a search box.
A larger app may need an incremental computation outside view. In the trading UI, sorted rows are needed both to display the table and to decide how focus moves; the derived interface permits sharing that computation between view and apply_action.
The pieces address our original goals: js_of_ocaml enables reuse of OCaml, the Elm architecture gives us a simple structure, and virtual DOM, Incremental and techniques such as partial rendering help keep large interfaces responsive.
Questions and answers
The audience asks about components, alternative frameworks, performance, testing and server communication. The discussion ends with handling asynchronous actions safely.
An audience member asks about higher-order components and comparisons with React, Reason and BuckleScript. I explain that at the time there is no explicit component concept in Incr_dom, although its pattern can be composed and an explicit approach is being developed.
I have not used the comparison frameworks enough to offer a definitive assessment. Incr_dom gives developers control over incremental performance; I have heard BuckleScript can lag OCaml versions, although its JavaScript output is more readable than js_of_ocaml output.
Asked about Elm, I say that the architecture shares many ideas and the languages are reportedly similar, but I do not know enough about Elm to judge how easy a developer would find the switch.
A question about large virtual DOM trees points out that obtaining the list of map data still requires reconstructing a list. We have noticed that cost and do not have a specific solution; avoiding reconstruction of individual nodes still saves work, and very large applications may need partial rendering.
Another audience member asks what belongs in model versus state. Only the model generates the view, so all information needed for that view belongs there; it must be immutable. Mutable resources such as server connections belong in state.
Asked about graphs and plots, I acknowledge that several interfaces need them and that the available graph-specific tooling is insufficient. Building better components would be useful.
Incremental computation has overhead, so it is not automatically worthwhile for every small model. If an application is already responsive, adding incremental machinery can hurt more than it helps; experimentation is needed to understand the tradeoff.
On debugging and testing, I mention console logging and the possibility of converting the virtual DOM to HTML for inspection. Testing tools were an area the team had discussed improving, but had not yet fully built.
The talk has focused on the client side. For server communication, Jane Street uses RPCs over WebSockets; a stream that pushes updates faster than the browser can process them may cause problems, so polling can sometimes be preferable.
For an asynchronous request, apply_action receives a schedule_action function that can schedule an action later. Other actions may run in the meantime, so the model may have changed by the time the deferred action is applied.
That race requires application-level care: check the current model before applying an asynchronously scheduled action to make sure it still makes sense. I thank the audience after the questions.
Más de este canal
15:33EN
1:48:29EN
1:36:37EN
1:05:45EN
1:25:00EN
1:16:29EN