updating doc
This commit is contained in:
parent
1067a65207
commit
a9095faa6f
22
README.md
22
README.md
|
@ -90,26 +90,6 @@ Here `Lwd.map : ('a -> 'b) -> 'a Lwd.t -> 'b Lwd.t` apply a transformation to a
|
|||
|
||||
When the `Link` is triggered, the counter is incremented. Because `document` depends on the value of the counter it is invalidated.
|
||||
|
||||
#### Optional: abstracting local state
|
||||
|
||||
This pattern of having local state that you want to manipulate in an almost purely functional way is very common and has been abstracted in the `Lwd_utils` library.
|
||||
|
||||
Here is another way to implement our button example:
|
||||
|
||||
```ocaml
|
||||
val Lwd_utils.local_state : ('a Lwd.t -> ('a -> unit) -> 'a * 'b) -> 'b
|
||||
|
||||
Lwd_utils.local_state (fun counter update ->
|
||||
let initial_value = 0 in
|
||||
let increment clicks () = update (clicks + 1) in
|
||||
let button clicks =
|
||||
Link (increment clicks,
|
||||
Text ("Clicked " ^ string_of_int clicks ^ " times"))
|
||||
in
|
||||
initial_value, Lwd.map button counter
|
||||
)
|
||||
```
|
||||
|
||||
### Building computation graph
|
||||
|
||||
`Lwd.t` implements a few abstractions that should be familiar to seasoned functional programmers:
|
||||
|
@ -215,4 +195,4 @@ The first question can be answered positively with a naive encoding: put `Lwd.va
|
|||
|
||||
To answer the second question, it is interesting to observe that there is no concept of "diffing" here. _Lwd_ does not try to see if things have changed in order to update them. Rather, if an input change, the whole branch that depends on it is recomputed.
|
||||
|
||||
While this might lead to inefficient recomputations. ...TODO...
|
||||
While this might lead to inefficient recomputations. ...TODO...
|
||||
|
|
|
@ -1,6 +1,14 @@
|
|||
val (let$) : 'a Lwd.t -> ('a -> 'b) -> 'b Lwd.t
|
||||
(** Alias to {!Lwd.map'} suitable for let-op bindings *)
|
||||
|
||||
val (let$*) : 'a Lwd.t -> ('a -> 'b Lwd.t) -> 'b Lwd.t
|
||||
(** Alias to {!Lwd.bind} suitable for let-op bindings *)
|
||||
|
||||
val (and$) : 'a Lwd.t -> 'b Lwd.t -> ('a * 'b) Lwd.t
|
||||
(** Alias to {!Lwd.pair} suitable for let-op bindings *)
|
||||
|
||||
val ($=) : 'a Lwd.var -> 'a -> unit
|
||||
(** Infix alias to {!Lwd.set} *)
|
||||
|
||||
val ($<-) : 'a Lwd_table.row -> 'a -> unit
|
||||
(** Infix alias to {!Lwd_table.set} *)
|
||||
|
|
|
@ -482,10 +482,10 @@ let fold_monoid map (zero, reduce) seq =
|
|||
|
||||
let monoid = (empty, concat)
|
||||
|
||||
let bind_list ls f =
|
||||
let transform_list ls f =
|
||||
Lwd_utils.map_reduce f monoid ls
|
||||
|
||||
let of_list ls = bind_list ls element
|
||||
let of_list ls = transform_list ls element
|
||||
|
||||
let rec of_sub_array f arr i j =
|
||||
if j < i then empty
|
||||
|
@ -494,9 +494,9 @@ let rec of_sub_array f arr i j =
|
|||
let k = i + (j - i) / 2 in
|
||||
concat (of_sub_array f arr i k) (of_sub_array f arr (k + 1) j)
|
||||
|
||||
let bind_array arr f = of_sub_array f arr 0 (Array.length arr - 1)
|
||||
let transform_array arr f = of_sub_array f arr 0 (Array.length arr - 1)
|
||||
|
||||
let of_array arr = bind_array arr element
|
||||
let of_array arr = transform_array arr element
|
||||
|
||||
let to_list x =
|
||||
let rec fold x acc = match x with
|
||||
|
|
|
@ -1,54 +1,46 @@
|
|||
(* Sequence construction
|
||||
(** {0 Sequence manipulation}
|
||||
|
||||
[Lwd_seq] implements a type of ordered collections with a pure interface.
|
||||
In addition, changes to collections are easy to track.
|
||||
[Lwd_seq] is an ordered collection with a pure interface.
|
||||
Changes to collections are easy to track.
|
||||
|
||||
A collection can be transformed with the usual map, filter and fold
|
||||
combinators. If later, the transformation is applied again to an updated
|
||||
collection, shared elements (in the sense of physical sharing), the
|
||||
result of the previous transformation will be reused for these elements.
|
||||
A collection can be transformed with the usual map, filter and fold
|
||||
combinators. If the collection is updated, shared elements (in the sense of
|
||||
physical sharing), the result of the previous transformation will be reused
|
||||
for these elements.
|
||||
|
||||
The book-keeping overhead is O(n) in the number of changes, so O(1) per
|
||||
element.
|
||||
The book-keeping overhead is O(n) in the number of changes, so O(1) per
|
||||
element.
|
||||
*)
|
||||
|
||||
type +'a t
|
||||
type +'a seq = 'a t
|
||||
(** The type of sequences *)
|
||||
|
||||
(** {1 Primitive constructors} *)
|
||||
|
||||
(* A sequence with no element. *)
|
||||
val empty : 'a seq
|
||||
(** A sequence with no element. *)
|
||||
|
||||
(* A singleton sequence. The physical identity of the element is considered
|
||||
when reusing previous computations.
|
||||
|
||||
If you do:
|
||||
let x1 = element x
|
||||
let x2 = element x
|
||||
|
||||
Then x1 and x2 are seen as different elements and no sharing will be done
|
||||
during transformation.
|
||||
*)
|
||||
val element : 'a -> 'a seq
|
||||
(** A singleton sequence. The physical identity of the element is considered
|
||||
when reusing previous computations.
|
||||
|
||||
(* Concatenate two sequences into a bigger one.
|
||||
As for [element], the physical identity of a sequence is considered for
|
||||
reuse.
|
||||
If you do:
|
||||
|
||||
{[let x1 = element x
|
||||
let x2 = element x]}
|
||||
|
||||
Then [x1] and [x2] are seen as different elements and no sharing will be
|
||||
done during transformation.
|
||||
*)
|
||||
|
||||
val concat : 'a seq -> 'a seq -> 'a seq
|
||||
(** Concatenate two sequences into a bigger one.
|
||||
As for [element], the physical identity of a sequence is considered for
|
||||
reuse.
|
||||
*)
|
||||
|
||||
val monoid : 'a t Lwd_utils.monoid
|
||||
val lwd_monoid : 'a t Lwd.t Lwd_utils.monoid
|
||||
|
||||
(*val bind : 'a seq -> ('a -> 'b seq) -> 'b seq*)
|
||||
val bind_list : 'a list -> ('a -> 'b seq) -> 'b seq
|
||||
val bind_array : 'a array -> ('a -> 'b seq) -> 'b seq
|
||||
|
||||
val of_list : 'a list -> 'a seq
|
||||
val of_array : 'a array -> 'a seq
|
||||
val to_list : 'a seq -> 'a list
|
||||
val to_array : 'a seq -> 'a array
|
||||
|
||||
(* Look at the contents of a sequence *)
|
||||
(** {1 Looking at sequence contents} *)
|
||||
|
||||
type ('a, 'b) view =
|
||||
| Empty
|
||||
|
@ -56,18 +48,44 @@ type ('a, 'b) view =
|
|||
| Concat of 'b * 'b
|
||||
|
||||
val view : 'a seq -> ('a, 'a seq) view
|
||||
(** View how a sequence is defined *)
|
||||
|
||||
(** {1 Conversion between sequences, lists and arrays} *)
|
||||
|
||||
val transform_list : 'a list -> ('a -> 'b seq) -> 'b seq
|
||||
(** Produce a sequence by transforming each element of a list and concatenating
|
||||
all results. *)
|
||||
|
||||
val transform_array : 'a array -> ('a -> 'b seq) -> 'b seq
|
||||
(** Produce a sequence by transforming each element of an array and
|
||||
concatenating all results. *)
|
||||
|
||||
val of_list : 'a list -> 'a seq
|
||||
(** Produce a sequence from a list *)
|
||||
|
||||
val of_array : 'a array -> 'a seq
|
||||
(** Produce a sequence from an array *)
|
||||
|
||||
val to_list : 'a seq -> 'a list
|
||||
(** Produce a list from a sequence *)
|
||||
|
||||
val to_array : 'a seq -> 'a array
|
||||
(** Produce an array from a sequence *)
|
||||
|
||||
(** {1 Balanced variant of sequences *)
|
||||
|
||||
module Balanced : sig
|
||||
(* A variant of the sequence type that guarantees that the depth of
|
||||
transformation, as measured in the number of [concat] nodes, grows in
|
||||
O(log n) where n is the number of elements in the sequnce.
|
||||
|
||||
(** A variant of the sequence type that guarantees that the depth of a
|
||||
transformation, measured as the number of nested [concat] nodes, grows in
|
||||
O(log n) where n is the number of elements in the sequnce.
|
||||
|
||||
This is useful to prevent stack overflows and to avoid degenerate cases
|
||||
where a single element change, but it is at the end of a linear sequence
|
||||
where a single element changes, but it is at the end of a linear sequence
|
||||
of [concat] nodes, thus making the total work O(n).
|
||||
For instance, in:
|
||||
|
||||
[concat e1 (concat e2 (concat e3 (... (concat e_n))...))]
|
||||
{[concat e1 (concat e2 (concat e3 (... (concat e_n))...))]}
|
||||
|
||||
If [e_n] changes, the whole spine has to be recomputed.
|
||||
|
||||
|
@ -79,7 +97,10 @@ module Balanced : sig
|
|||
only useful to balance the first sequence of the pipeline. Derived
|
||||
sequence will have a depth bounded by the depth of the first one.
|
||||
*)
|
||||
|
||||
type 'a t = private 'a seq
|
||||
(** Type of balanced sequences *)
|
||||
|
||||
val empty : 'a t
|
||||
val element : 'a -> 'a t
|
||||
val concat : 'a t -> 'a t -> 'a t
|
||||
|
@ -87,38 +108,51 @@ module Balanced : sig
|
|||
val view : 'a t -> ('a, 'a t) view
|
||||
end
|
||||
|
||||
(* Lwd interface.
|
||||
(** {1 Transforming sequences} *)
|
||||
|
||||
(**
|
||||
All sequences live in [Lwd] monad: if a sequence changes slightly, parts
|
||||
that have not changed will not be re-transformed.
|
||||
*)
|
||||
|
||||
(* [fold ~map ~reduce] transforms a sequence.
|
||||
If the sequence is non-empty, the [map] function is applied to element nodes
|
||||
and the [reduce] function is used to combine transformed concatenated nodes.
|
||||
If the sequence is empty, None is returned.
|
||||
*)
|
||||
val fold :
|
||||
map:('a -> 'b) -> reduce:('b -> 'b -> 'b) -> 'a seq Lwd.t -> 'b option Lwd.t
|
||||
(** [fold ~map ~reduce] transforms a sequence.
|
||||
If the sequence is non-empty, the [map] function is applied to element
|
||||
nodes and the [reduce] function is used to combine transformed concatenated
|
||||
nodes.
|
||||
If the sequence is empty, None is returned.
|
||||
*)
|
||||
|
||||
val fold_monoid :
|
||||
('a -> 'b) -> 'b Lwd_utils.monoid -> 'a seq Lwd.t -> 'b Lwd.t
|
||||
(** Like [fold], but reduction and default value are defined by a [monoid] *)
|
||||
|
||||
(* [map f] transforms a sequence by applying [f] to each element. *)
|
||||
val map :
|
||||
('a -> 'b) -> 'a seq Lwd.t -> 'b seq Lwd.t
|
||||
(** [map f] transforms a sequence by applying [f] to each element. *)
|
||||
|
||||
val filter :
|
||||
('a -> bool) -> 'a seq Lwd.t -> 'a seq Lwd.t
|
||||
(** [filter p] transforms a sequence by keeping elements that satisfies [p]. *)
|
||||
|
||||
val filter_map :
|
||||
('a -> 'b option) -> 'a seq Lwd.t -> 'b seq Lwd.t
|
||||
(** Filter and map elements at the same time *)
|
||||
|
||||
val lift : 'a Lwd.t seq Lwd.t -> 'a seq Lwd.t
|
||||
(** Remove a layer of [Lwd] inside a sequence. *)
|
||||
|
||||
val bind : 'a seq Lwd.t -> ('a -> 'b seq) -> 'b seq Lwd.t
|
||||
(** Sequence forms a monad too... *)
|
||||
|
||||
(* Low-level interface *)
|
||||
val monoid : 'a t Lwd_utils.monoid
|
||||
(** Monoid instance for sequences *)
|
||||
|
||||
val lwd_monoid : 'a t Lwd.t Lwd_utils.monoid
|
||||
(** Monoid instance for reactive sequences *)
|
||||
|
||||
(** {1 Low-level interface for observing changes} *)
|
||||
|
||||
module Reducer : sig
|
||||
(* The interface allows to implement incremental sequence transformation
|
||||
|
|
|
@ -1,29 +1,102 @@
|
|||
(** {0 Table manipulation}
|
||||
|
||||
[Lwd_table] is an ordered collection with an impure interface.
|
||||
It is designed to be efficient in an interactive setting.
|
||||
|
||||
The interface mimics the one of a doubly-linked lists: from a node, called
|
||||
row, you can iterate backward and forward, insert and delete other nodes,
|
||||
and change the value it is bound to.
|
||||
|
||||
The sequence of nodes can be observed by map/reduce operations, that will
|
||||
be recomputed efficiently when sequence changes.
|
||||
*)
|
||||
|
||||
type 'a t
|
||||
type 'a row
|
||||
(** The type of tables *)
|
||||
|
||||
val make : unit -> 'a t
|
||||
val clear : 'a t -> unit
|
||||
(** Create a new table *)
|
||||
|
||||
(** {1 Inserting rows} *)
|
||||
|
||||
val prepend : ?set:'a -> 'a t -> 'a row
|
||||
(** Insert and return a new row at the start of a table.
|
||||
It can be optionnally initialized to the value of [set]. *)
|
||||
|
||||
val append : ?set:'a -> 'a t -> 'a row
|
||||
(** Insert and return a new row at the end of a table.
|
||||
It can be optionnally initialized to the value of [set]. *)
|
||||
|
||||
val prepend' : 'a t -> 'a -> unit
|
||||
(* Insert a new initialized row at start of a table *)
|
||||
|
||||
val append' : 'a t -> 'a -> unit
|
||||
(* Insert a new initialized row at end of a table *)
|
||||
|
||||
val before : ?set:'a -> 'a row -> 'a row
|
||||
(** Insert and return a new row just before an existing row.
|
||||
It can be optionnally initialized to the value of [set].
|
||||
|
||||
If the input row is unbound ([is_bound] returns false), the returned row is
|
||||
too.
|
||||
*)
|
||||
|
||||
val after : ?set:'a -> 'a row -> 'a row
|
||||
(** Insert and return a new row just after an existing row.
|
||||
It can be optionnally initialized to the value of [set].
|
||||
|
||||
If the input row is unbound ([is_bound] returns false), the returned row is
|
||||
too.
|
||||
*)
|
||||
|
||||
(** {1 Iterating over rows} *)
|
||||
|
||||
val first : 'a t -> 'a row option
|
||||
(** Returns the first row of a table, or [None] if the table is empty *)
|
||||
|
||||
val last : 'a t -> 'a row option
|
||||
(** Returns the last row of a table, or [None] if the table is empty *)
|
||||
|
||||
val next : 'a row -> 'a row option
|
||||
(** Returns the row next to another one, or [None] if the input row is unbound
|
||||
or is the last row *)
|
||||
|
||||
val prev : 'a row -> 'a row option
|
||||
(** Returns the row just before another one, or [None] if the input row is
|
||||
unbound or is the first row *)
|
||||
|
||||
(** {1 Accessing and changing row contents} *)
|
||||
|
||||
val get : 'a row -> 'a option
|
||||
(** Get the value associated with a row, if any, or [None] if the row is
|
||||
unbound *)
|
||||
|
||||
val set : 'a row -> 'a -> unit
|
||||
(** Set the value associated with a row, or do nothing if the row is unbound *)
|
||||
|
||||
val unset : 'a row -> unit
|
||||
(** Unset the value associated with a row *)
|
||||
|
||||
(** {1 Removing rows} *)
|
||||
|
||||
val is_bound : 'a row -> bool
|
||||
(** Returns [true] iff the row is bound in a table (it has not beem [remove]d
|
||||
yet, the table has not been [clear]ed) *)
|
||||
|
||||
val remove : 'a row -> unit
|
||||
(** [remove] a row from its table, [is_bound] will be [true] after that *)
|
||||
|
||||
val clear : 'a t -> unit
|
||||
(** Remove all rows from a table *)
|
||||
|
||||
(** {1 Observing table contents} *)
|
||||
|
||||
val reduce : 'a Lwd_utils.monoid -> 'a t -> 'a Lwd.t
|
||||
(** Observe the content of a table by reducing it with a monoid *)
|
||||
|
||||
val map_reduce : ('a row -> 'a -> 'b) -> 'b Lwd_utils.monoid -> 'a t -> 'b Lwd.t
|
||||
(** Observe the content of a table by mapping and reducing it *)
|
||||
|
||||
val iter : ('a -> unit) -> 'a t -> unit
|
||||
(** Immediate, non reactive, iteration over elements of a table *)
|
||||
|
|
|
@ -17,7 +17,7 @@ let map_reduce inj (zero, plus) items =
|
|||
| (_,x) :: xs ->
|
||||
List.fold_left (fun acc (_, v) -> plus v acc) x xs
|
||||
|
||||
let pure_pack monoid items = map_reduce (fun x -> x) monoid items
|
||||
let reduce monoid items = map_reduce (fun x -> x) monoid items
|
||||
|
||||
let rec cons_lwd_monoid plus c xs v =
|
||||
match xs with
|
||||
|
@ -37,21 +37,6 @@ let pack_seq (zero, plus) items =
|
|||
| (_,x) :: xs ->
|
||||
List.fold_left (fun acc (_, v) -> Lwd.map2 plus v acc) x xs
|
||||
|
||||
let local_state f =
|
||||
let r = ref None in
|
||||
let acquire () = match !r with
|
||||
| None -> invalid_arg "Lwd_utils.trace: cyclic evaluation"
|
||||
| Some v -> v
|
||||
in
|
||||
let prim = Lwd.prim ~acquire ~release:ignore in
|
||||
let update v =
|
||||
r := Some v;
|
||||
Lwd.invalidate prim
|
||||
in
|
||||
let v, result = f (Lwd.get_prim prim) update in
|
||||
r := Some v;
|
||||
result
|
||||
|
||||
let rec map_l (f:'a -> 'b Lwd.t) (l:'a list) : 'b list Lwd.t =
|
||||
match l with
|
||||
| [] -> Lwd.return []
|
||||
|
|
|
@ -1,14 +1,36 @@
|
|||
type 'a monoid = 'a * ('a -> 'a -> 'a)
|
||||
(** A monoid, defined by a default element and an associative operation *)
|
||||
|
||||
val lift_monoid : 'a monoid -> 'a Lwd.t monoid
|
||||
(** Use a monoid inside [Lwd] *)
|
||||
|
||||
(** {1 List reduction functions}
|
||||
|
||||
All reductions are balanced, relying on operator associativity.
|
||||
While [fold_{left,right}] would compute a chain like:
|
||||
[fold f [a; b; c; d] = f a (f b (f c d)]
|
||||
[reduce] uses tree-shaped computations like:
|
||||
[reduce f [a; b; c; d] = f (f a b) (f c d)]
|
||||
|
||||
The depth of the computation grows in O(log n) where n is the length of the
|
||||
input sequence.
|
||||
*)
|
||||
|
||||
val pack : 'a monoid -> 'a Lwd.t list -> 'a Lwd.t
|
||||
(** Reduce a list of elements in [Lwd] monad *)
|
||||
|
||||
val pack_seq : 'a monoid -> 'a Lwd.t Seq.t -> 'a Lwd.t
|
||||
val pure_pack : 'a monoid -> 'a list -> 'a
|
||||
(** Reduce an (OCaml) [Seq.t] with a monoid *)
|
||||
|
||||
val reduce : 'a monoid -> 'a list -> 'a
|
||||
(** Reduce a list with a monoid **)
|
||||
|
||||
val map_reduce : ('a -> 'b) -> 'b monoid -> 'a list -> 'b
|
||||
(** Map and reduce a list with a monoid **)
|
||||
|
||||
val local_state : ('a Lwd.t -> ('a -> unit) -> 'a * 'b) -> 'b
|
||||
(** {1 Other Lwd list functions} *)
|
||||
|
||||
val map_l : ('a -> 'b Lwd.t) -> 'a list -> 'b list Lwd.t
|
||||
|
||||
val flatten_l : 'a Lwd.t list -> 'a list Lwd.t
|
||||
(** Commute [Lwd] and [list] *)
|
||||
|
|
|
@ -618,11 +618,11 @@ let grid
|
|||
Ui.resize ~w:col_widths.(i) ~h:row_h ?crop ?fill ?bg c)
|
||||
row
|
||||
in
|
||||
Lwd_utils.pure_pack pack_pad_x row)
|
||||
Lwd_utils.reduce pack_pad_x row)
|
||||
rows
|
||||
in
|
||||
(* TODO: mouse and keyboard handling *)
|
||||
let ui = Lwd_utils.pure_pack pack_pad_y rows in
|
||||
let ui = Lwd_utils.reduce pack_pad_y rows in
|
||||
Lwd.return ui
|
||||
|
||||
let button ?attr s f =
|
||||
|
|
|
@ -318,9 +318,9 @@ struct
|
|||
let pack_y = (empty, join_y)
|
||||
let pack_z = (empty, join_z)
|
||||
|
||||
let hcat xs = Lwd_utils.pure_pack pack_x xs
|
||||
let vcat xs = Lwd_utils.pure_pack pack_y xs
|
||||
let zcat xs = Lwd_utils.pure_pack pack_z xs
|
||||
let hcat xs = Lwd_utils.reduce pack_x xs
|
||||
let vcat xs = Lwd_utils.reduce pack_y xs
|
||||
let zcat xs = Lwd_utils.reduce pack_z xs
|
||||
|
||||
let has_focus t = Focus.has_focus t.focus
|
||||
|
||||
|
|
Loading…
Reference in New Issue