Conference Presentation, Keynote
Introduction to Incr_dom: Writing Dynamic Web Apps in OCaml
Project Overview: IncurDOM is a web UI framework developed at Jane Street, primarily for internal trading systems, designed to write efficient, maintainable web interfaces in OCaml.
- Target Use Case: Displays large datasets (e.g., 10,000+ rows) with high-frequency updates (tens of updates per second) where users view and filter data but rarely modify the underlying dataset directly.
- Key Challenge: Maintaining responsiveness and efficiency when sorting, filtering, or editing cells within massive tables without browser lag.
Design Goals:
- Ease of Use: Allow developers to write web UIs with the same ease as standard OCaml applications, avoiding the need for deep JavaScript expertise.
- Maintainability: Enable sharing of business logic between backend (OCaml) and frontend (JS via JS_of_OCaml), ensuring backend changes automatically propagate to the client or trigger compiler errors.
- Performance: Optimize for large data volumes and frequent state changes through partial rendering, virtual DOM diffing, and incremental computations.
Architectural Foundations:
- JS_of_OCaml: A compiler that runs OCaml bytecode in the browser, allowing shared code between server and client and leveraging OCaml's type safety and tooling.
- Elm Architecture: Adopts a strict Model-View-Update pattern:
- Model: Immutable state of the application.
- View: A declarative function generating an HTML-like structure based on the model.
- Actions: Events that trigger state updates; actions are scheduled by the view and applied by
apply_action.
- Virtual DOM: Stores a lightweight in-memory copy of the DOM to compute differences (diffing) before patching the actual browser DOM, reducing expensive browser reflows.
Core Optimization Techniques:
- Incremental Computations: Uses the
Incrementallibrary to build a dependency graph where only nodes affected by input changes are recomputed.- Cutoffs: An optimization where if a node's value remains unchanged despite parent updates, its descendants are not recomputed, saving significant processing time.
- IncrMap: A specialized library for efficient handling of maps, enabling O(diff size) updates for large datasets (e.g., table rows) rather than O(n) reconstruction.
- Partial Rendering: Renders only rows currently visible in the viewport; off-screen rows are replaced with placeholders to prevent DOM bloat.
- Incremental Computations: Uses the
Demonstration Workflow:
- Initial State: A simple app with
unitmodel and action types displayed a static header. - State Introduction: The model was updated to an
int listof 100 random counters, displaying a table with row indices and values. - Styling: Dynamic CSS styles (colors) applied to cells based on value ranges (green <10, blue <100, red >100).
- Interactivity:
- Single click: Increments a counter.
- Double click: Resets a counter.
- Auto-update: Background thread schedules random increments ~20 times/second.
- Performance Scaling:
- Switching from
listtoMapimproved performance but still requiredmap.datato reconstruct the full list for the table, causing lag at 30,000 rows. - Applying
IncrMapand incremental view functions significantly smoothed scrolling and updates, though initial load time remained high for large datasets. - Added a live "total" sum calculated via
unordered_foldon the map, demonstrating efficient real-time aggregation.
- Switching from
- Initial State: A simple app with
Advanced Patterns & Considerations:
- State vs. Model: The
modelmust be immutable and contain all data required for rendering; mutable external state (e.g., server connections) belongs in a separatestatemodule. - Synchronization: Asynchronous actions require explicit model checks before application to ensure validity against the current state version.
- Focus Management: Custom functions (
on_display,update_visibility) are required to keep specific rows (e.g., focused cells) within the viewport after sorting or filtering. - Partial Rendering Implementation: Required for production-scale apps to handle 10,000+ rows; involves injecting placeholders for non-visible rows and managing scroll offsets.
- State vs. Model: The
Comparative Analysis (Q&A Insights):
- Vs. React: IncurDOM lacks explicit component syntax (though composition is natural via the architecture) and relies on OCaml rather than a JS-like syntax.
- Vs. BuckleScript/Reason React: Jane Street prefers JS_of_OCaml over BuckleScript because BuckleScript lags behind the latest OCaml versions, despite BuckleScript producing readable JavaScript.
- Vs. Elm: IncurDOM enforces the Elm architecture more strictly than Elm, which allows flexibility but requires manual pattern adherence. Elm uses commands/subscriptions for side effects; IncurDOM uses the
schedule_actionmechanism within the view. - Limitations:
Virtual DOMstill requires reconstructing the list of children from maps for table rendering, which is a known bottleneck for massive datasets requiring partial rendering.
Future Roadmap & Gaps:
- Components: Explicit component abstractions are currently being developed by the team to improve code reusability.
- Graph Support: Lack of tooling for rendering dynamic graphs; identified as a necessary area for future library expansion.
- Testing: No dedicated testing frameworks yet; current methods rely on console logging and manual browser inspection.
- RPC Best Practices: Recommends polling over persistent WebSocket pipelines to prevent browser crashes during high-frequency updates.
Developer Guidelines:
- Incremental computations should only be used when the overhead of maintaining the dependency graph is justified by the data size or update frequency; small, static models can be treated as constants to avoid unnecessary complexity.
- Asynchronous operations should schedule actions immediately, with validation checks applied in the
apply_actionphase to handle potential model state drift.