Data Driven UIs, Incrementally
Yaron Minsky / Jane Street Group
Your UI as two functions
Action
Model
Vdom
module type Component = sig
type model
type action
val apply_action : model -> action -> model
val render : model -> Vdom.t
endYour UI as an Incremental Computation
Model
Vdom
DOM
Model'
Vdom'
DOM'
Incrementality with Incremental
- Your computation as a dependency graph!
- Based on Acar's Self Adjusting Computations
Map and Map2
Model
Sub 1
Sub 2
Vdom 1
Vdom 2
Vdom
val map : 'a Incr.t -> ('a -> 'b) -> 'b Incr.tval map2 :
'a Incr.t -> 'b Incr.t -> ('a -> 'b -> 'c) -> 'c Incr.tBind
Model
Sub 1
Sub 2
Vdom 1
Vdom 2
Vdom
val bind : 'a Incr.t -> ('a -> 'b Incr.t) -> 'b Incr.tIncremental thus far
- Map works for static structures
- Bind adds (limited) dynamism
- But how can we be dynamic and incremental?
Mapping over an incremental map
val incr_map
: ('k, 'v1, 'cmp) Map.t Incr.t
-> ('v1 -> 'v2)
-> ('k, 'v2, 'cmp) Map.t Incr.tm
let incr_map m f =
let%map m = m in
Map.map m fm'
Diffing Maps
val symmetric_diff
: ('k, 'v, 'cmp) Map.t
-> ('k, 'v, 'cmp) Map.t
-> data_equal:('v -> 'v -> bool)
-> ('k * [ `Left of 'v
| `Right of 'v
| `Unequal of 'v * 'v ]) Sequence.tHooking in Diffs with diff_map
val diff_map
: 'a Incr.t
-> (('a * 'b) option -> 'a -> 'b)
-> 'b Incr.tlet diff_map i f =
let old = ref None in
let%map a = i in
let b = f !old a in
old := Some (a, b);
bImplementing incr_map
let incr_map m f =
diff_map m (fun old m ->
match old with
| None -> Map.map m ~f
| Some (old_in, old_out) ->
let diff =
Map.symmetric_diff old_in m
~data_equal:phys_equal
in
Sequence.fold diff ~init:old_out
~f:(fun acc (key,change) ->
match change with
| `Left _ -> Map.remove acc key
| `Unequal (_,data) | `Right data ->
Map.set acc ~key ~data:(f data)))Mapping an incremental function over an incremental map
val incr_map'
: ('k, 'v1, 'cmp) Map.t Incr.t
-> ('v1 Incr.t -> 'v2 Incr.t)
-> ('k, 'v2, 'cmp) Map.t Incr.tMapping an incremental function over an incremental map
m
e
e
e
e
e'
e'
e'
e'
m'
Split and Join
val split
: ('k,'v ,'cmp) Map.t Incr.t
-> ('k,'v Incr.t,'cmp) Map.t Incr.tval join
: ('k,'v Incr.t,'cmp) Map.t Incr.t
-> ('k,'v ,'cmp) Map.t Incr.tlet incr_map' m f =
join (incr_map (split m) f)Extending Incremental
- diff_map works on any diffable data structure
- Split and Join, on top of Incremental.Expert
- Incr_map provides Map-specific primitives
- Incr_select for range and focus operations
Things to remember
- UI design is an optimization problem
- SAC is a powerful optimization tool
- Diff and Patch are incremental functional glue
https://github.com/janestreet/incr_dom
https://opensource.janestreet.com
Beyond the browser
Server
Client
Full
model
Client
view
Model
Vdom
DOM
Full
model
Client
view
Model
Vdom
DOM
Data Driven UIs, Incrementally (Strangeloop 2018)
By Yaron Minsky
Data Driven UIs, Incrementally (Strangeloop 2018)
- 410