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
end

Your 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.t
val map2 : 
  'a Incr.t -> 'b Incr.t -> ('a -> 'b -> 'c) -> 'c Incr.t

Bind

Model

Sub 1

Sub 2

Vdom 1

Vdom 2

Vdom

val bind : 'a Incr.t -> ('a -> 'b Incr.t) -> 'b Incr.t

Incremental 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.t

m

let incr_map m f =
  let%map m = m in
  Map.map m f

m'

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.t

Hooking in Diffs with diff_map

val diff_map
  :  'a Incr.t
  -> (('a * 'b) option -> 'a -> 'b)
  -> 'b Incr.t
let diff_map i f =
  let old = ref None in
  let%map a = i in
  let b = f !old a in
  old := Some (a, b);
  b

Implementing 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.t

Mapping 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.t
val join
  :  ('k,'v Incr.t,'cmp) Map.t Incr.t
  -> ('k,'v       ,'cmp) Map.t Incr.t
let incr_map' m f =
  join (incr_map (split m) f)

Extending Incremental

  • diff_map works on any diffable data structure
  • Incremental.Expert gives a low-level interface
    • Incr_map for Map-specific primitives
    • Incr_select for range and focus operations

Beyond the browser

Server

Client

Full

model

Client

view

Model

Vdom

DOM

Full

model

Client

view

Model

Vdom

DOM

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

Join us!

http://janestreet.com/apply

Interviewing for co-op/internship and full-time positions

Data Driven UIs, Incrementally (Campus Talk)

By Yaron Minsky

Data Driven UIs, Incrementally (Campus Talk)

  • 404