Daily Tech Briefing
AI 科技速览
每天 5 分钟内学习 AI。获取最新的人工智能新闻,理解其重要性,并学习如何将其应用于您的工作。
Hacker News · 2026/8/3 08:29:57

Bonsai: Janestreet's UI Library
AI 中文解读
Jane Street这家华尔街顶级量化交易公司,开源了自己的前端界面库Bonsai。它的最吸引人之处在于:用函数式编程语言OCaml写网页界面,既快又稳,还能让前后端用同一套代码和类型,大幅减少出错。
通俗来说,Bonsai像是React的“升级版”,但理念更超前。它把界面拆成一个个纯函数状态机,每个部分独立管理自身状态,只有数据真正变化时才重新渲染,效率极高。这就好比装修房间,传统方式刷一面墙也要重新搬家具,而Bonsai只动需要动的角落。而且因为状态管理被内置,开发复杂交互界面时不用手动层层传递数据,省心不少。
这项技术主要影响的是开发者群体:它能帮企业用更少的人力维护大型Web应用,尤其适合交易、金融这类对实时性和稳定性要求极高的场景。对普通人来说,未来用到的网页应用可能会更流畅、响应更快,故障更少——比如看行情或在线交易时,界面不会再卡顿掉链子。虽然日常用户感受不到背后的技术,但体验升级是实实在在的。
Bonsai
Bonsai
Bonsai is a UI library for building performant, reactive web applications in OCaml, partly
inspired by Elm. It is used to build almost all web applications
inside Jane Street, everything from the corporate directory to tools that monitor and
interact with our trading systems. A simple Bonsai component with a little interactivity
looks like this:
module Dice = struct
let faces =
[ "⚀"; "⚁"; "⚂"; "⚃"; "⚄"; "⚅" ]
;;
let component (graph @ local) =
(* Components are implemented as purely functional state machines. *)
let face, set_face = Bonsai.state (List.hd_exn faces) graph in
(* Components are incrementally rendered, only when the relevant parts of the state change. *)
let%arr face and set_face in
{%html|
<div>
You rolled a #{face}
<button
style="" on_click=%{fun _ ->
let index = Random.int (List.length faces) in
set_face (List.nth_exn faces index)}
>
Roll the dice
</button>
</div>
|}
;;
end
Most internal Jane Street web applications are built with Bonsai
Components are implemented as purely functional state machines, and are easily
composable. Incrementalization inside the framework means that values don’t get recomputed
until necessary. This applies to every value, not just the view.
Why Bonsai?
Other web frameworks tend to lump together state, incrementality, and rendering into a
single abstraction, the UI component. By contrast, Bonsai allows you to compose state and
incrementality primitives a la carte. The same primitives that prevent re-rendering the
entire page during user interaction can also be used to incrementalize an expensive
business logic computation on a live-updating dataset. (If you're used to React, imagine
if everything used something very similar to hooks, and state was managed outside of the
component hierarchy.)
Since state is not associated with explicit components, there is an extensive API for
managing the lifecycle and scoping of state as users interact with the page. For instance,
if you wanted to embed a collection of stateful UI components inside another UI component
(in a tabbed interface, say), Bonsai will handle the state management for you instead of
requiring that you manually hoist every internal component's state to the app's top-level
model. For more examples of how state is composed, see this composition comparison
written by the creator of Bonsai.
And because Bonsai is written in OCaml, it becomes possible to use the same language and
types on both the backend and frontend. It's hard to overstate the impact this has on
legibility and keeping a large web app's codebase manageable, especially when you make
pervasive use of OCaml's type system to reduce errors. At Jane Street, many internal
systems previously only had terminal UIs, and the framework has made it easy to port the
existing types and business logic to the web.
Bonsai also comes with a powerful templating language, support for component-specific
stylesheets, and a system for whole-app automated tests.
Expressive tests that save you from manually clicking through your app
One of Bonsai’s most powerful features is its ability to let you easily write realistic
tests, in which you programatically manipulate UI elements and watch your DOM evolve.
In the following example, we’re testing the behavior of a user-selector. Whatever you type
in the text box gets appended to a little “hello” message:
let%expect_test "shows hello to a specified user" =
let handle = Handle.create (Result_spec.vdom Fn.id) hello_textbox in
Handle.show handle;
[%expect
{|
<div>
<input oninput> </input>
<span> hello </span>
</div> |}];
Handle.input_text handle ~get_vdom:Fn.id ~selector:"input" ~text:"Bob";
Handle.show_diff handle;
[%expect
{|
<div>
<input oninput> </input>
- <span> hello </span>
+ <span> hello Bob </span>
分享
阅读原文 ↗