Why Bonsai?
The successor to Incr_dom, Bonsai is a front-end web framework following the Elm style of a functional user-interface API.
Composition
UI Components are implemented as purely functional state-machines, and are composible in multiple ways:
- Parallel: Components can be built out of sub-components, and these sub-components don’t know about one another.
- Serial: Components can compute values that are used downstream by other components.
Incremental
Incrementalization inside the framework means that values don’t get recomputed until necessary. This applies to every value, not just the view.
(* An example of a small Bonsai component for creating counter
widgets ("+" and "-" buttons allow you to increment and decrement a
number). The state of each counter is housed in a shared [Map]. Adding
a new counter is as simple as adding a value to the map. *)
module Model = struct
type t = int Int.Map.t [@@deriving sexp, equal]
end
module Action = struct
type t =
| New
| Update of int * int
[@@deriving sexp]
end
let default_model = Int.Map.empty
let apply_action ~inject:_ ~schedule_event:_ model = function
| Action.New -> Map.add_exn model ~key:(Map.length model) ~data:0
| Update (location, diff) ->
Map.update model location ~f:(function
| None -> 1
| Some x -> x + diff)
;;
let component =
let%sub state =
Bonsai.state_machine0
(module Model)
(module Action)
~default_model
~apply_action
in
let%arr state, inject = state in
let button text action =
Node.button
~attr:(Attr.on_click (fun _ -> inject action))
[ Node.text text ]
in
let add_button = button "add" New in
let for_each i c =
Node.div
[ button "-1" (Update (i, -1))
; Node.textf "%d" c
; button "+1" (Update (i, 1)) ]
in
let counters = state |> Map.data |> List.mapi ~f:for_each in
Node.div (add_button :: counters)
;;
Learn More
Sources
If you want to understand the design of the Bonsai API, it might be helpful to read this paper on the arrow calculus by Lindley et al.