Library
Module
Module type
Parameter
Class
Class type
module Private_computation := Computation
module Private_value := Value
module type Model = sig ... end
module type Action = sig ... end
module type Enum = sig ... end
module type Comparator = sig ... end
The functions found in this module are focused on the manipulation of values of type 'a Computation.t
and 'a Value.t
. There are fine descriptions of these types below and how to use them, but since it's so common to convert between the two, here is a cheat-sheet matrix for converting between values of different types:
| Have \ Want | 'a Value.t | 'a Computation.t | |------------------+------------------------+------------------| | 'a | let v = Value.return a | let c = const a | | 'a Value.t | | let c = read v | | 'a Computation.t | let%sub v = c | |
module Value : sig ... end
module Computation : sig ... end
module Effect = Ui_effect
module Var : sig ... end
val read : 'a Value.t -> 'a Computation.t
Converts a Value.t
to a Computation.t
. Unlike most Computations, the Computation.t
returned by read
can be used in multiple locations without maintaining multiple copies of any models or building duplicate incremental graphs.
read
is most commonly used in the final expression of a let%sub
chain, like so:
fun i ->
let%sub a = f i in
let%sub b = g i in
read
(let%map a = a
and b = b in
a + b)
or to use some APIs that require Computation.t
like so:
val cond : bool Value.t
val x : 'a Value.t
val some_computation : 'a Computation.t
let y = if_ cond ~then_:some_computation ~else_:(read x)
val y : 'a Computation.t
val const : 'a -> 'a Computation.t
Creates a Computation.t
that provides a constant value.
val path_id : string Computation.t
Retrieves the path to the current computation as a string. This string is not human-readable, but can be used as an ID which is unique to this particular instance of a component.
val pure : ('a -> 'b) -> 'a Value.t -> 'b Computation.t
Lifts a regular OCaml function into one that takes a Value as input, and produces a Computation as output.
val of_module0 :
(module Bonsai__.Import.Component_s
with type Action.t = 'a
and type Input.t = unit
and type Model.t = 'm
and type Result.t = 'r) ->
default_model:'m ->
'r Computation.t
Given a first-class module that has no input (unit input type), and the default value of the state machine, of_module0
will create a Computation
that produces values of that module's Result.t
type.
val of_module1 :
(module Bonsai__.Import.Component_s
with type Action.t = 'a
and type Input.t = 'i
and type Model.t = 'm
and type Result.t = 'r) ->
default_model:'m ->
'i Value.t ->
'r Computation.t
The same as of_module0
, but this one has an input type 'i
. Because input to the component is required, this function also expects a Value.t
that provides its input. It is common for this function to be partially applied like so:
val a : int Value.t
val b : int Value.t
let f = of_module1 (module struct ... end) ~default_model in
let%sub a = f a in
let%sub b = f b in
...
Where the Value.t
values are passed in later.
val of_module2 :
(module Bonsai__.Import.Component_s
with type Action.t = 'a
and type Input.t = 'i1 * 'i2
and type Model.t = 'm
and type Result.t = 'r) ->
default_model:'m ->
'i1 Value.t ->
'i2 Value.t ->
'r Computation.t
The same as of_module1
but with two inputs.
val state_machine0 :
Core.Source_code_position.t ->
(module Model with type t = 'model) ->
(module Action with type t = 'action) ->
default_model:'model ->
apply_action:
(inject:('action -> unit Effect.t) ->
schedule_event:(unit Effect.t -> unit) ->
'model ->
'action ->
'model) ->
('model * ('action -> unit Effect.t)) Computation.t
A constructor for Computation.t
that models a simple state machine. The first-class module implementing Model
describes the states in the state machine, while the first-class module implementing Action
describes the transitions between states.
default_model
is the initial state for the state machine, and apply_action
implements the transition function that looks at the current state and the requested transition, and produces a new state.
(It is very common for inject
and schedule_event
to be unused)
val actor0 :
Core.Source_code_position.t ->
(module Model with type t = 'model) ->
(module Action with type t = 'action) ->
default_model:'model ->
recv:
(schedule_event:(unit Effect.t -> unit) ->
'model ->
'action ->
'model * 'return) ->
('model * ('action -> 'return Effect.t)) Computation.t
Identical to actor1
but it takes 0 inputs instead of 1.
val actor1 :
Core.Source_code_position.t ->
(module Model with type t = 'model) ->
(module Action with type t = 'action) ->
default_model:'model ->
recv:
(schedule_event:(unit Effect.t -> unit) ->
'input ->
'model ->
'action ->
'model * 'return) ->
'input Value.t ->
('model * ('action -> 'return Effect.t)) Computation.t
actor1
is very similar to state_machine1
, with two major exceptions:
- the
apply-action
function for state-machine is renamedrecv
, and it returns a "response", in addition to a new model. - the 2nd value returned by the component allows for the sender of an action to handle the effect and read the response.
Because the semantics of this function feel like an actor system, we've decided to name the function accordingly.
val state :
Core.Source_code_position.t ->
(module Model with type t = 'model) ->
default_model:'model ->
('model * ('model -> unit Effect.t)) Computation.t
A frequently used state-machine is the trivial 'set-state' transition, where the action always replaces the value contained inside. This helper-function implements that state-machine, providing access to the current state, as well as an inject function that updates the state.
val state_opt :
Core.Source_code_position.t ->
?default_model:'model ->
(module Model with type t = 'model) ->
('model option * ('model option -> unit Effect.t)) Computation.t
Similar to state
, but stores an option of the model instead. default_model
is optional and defaults to None
.
val state_machine1 :
Core.Source_code_position.t ->
(module Model with type t = 'model) ->
(module Action with type t = 'action) ->
default_model:'model ->
apply_action:
(inject:('action -> unit Effect.t) ->
schedule_event:(unit Effect.t -> unit) ->
'input ->
'model ->
'action ->
'model) ->
'input Value.t ->
('model * ('action -> unit Effect.t)) Computation.t
The same as state_machine0
, but apply_action
also takes an input from a Value.t
.
val lazy_ : 'a Computation.t Core.Lazy.t -> 'a Computation.t
Because all Bonsai computation-returning-functions are eagerly evaluated, attempting to use "let rec" to construct a recursive component will recurse infinitely. One way to avoid this is to use a lazy computation and Bonsai.lazy_
to defer evaluating the Computation.t
.
let rec some_component arg1 arg2 =
...
let _ = Bonsai.lazy_ (lazy (some_component ...)) in
...
val assoc :
('key, 'cmp) comparator ->
('key, 'data, 'cmp) Core.Map.t Value.t ->
f:('key Value.t -> 'data Value.t -> 'result Computation.t) ->
('key, 'result, 'cmp) Core.Map.t Computation.t
assoc
is used to apply a Bonsai computation to each element of a map. This function signature is very similar to Map.mapi
or Incr_map.mapi'
, and for good reason!
It is doing the same thing (taking a map and a function and returning a new map with the function applied to every key-value pair), but this function does it with the Bonsai values, which means that the computation is done incrementally and also maintains a state machine for every key-value pair.
val enum :
(module Enum with type t = 'k) ->
match_:'k Value.t ->
with_:('k -> 'a Computation.t) ->
'a Computation.t
enum
is used for matching on a value and providing different behaviors on different values. The type of the value must be enumerable (there must be a finite number of possible values), and it must be comparable and sexpable.
The rest of the parameters are named like you might expect from pattern-matching syntax, with match_
taking the value to match on, and with_
taking a function that choose which behavior to use.
val wrap :
(module Model with type t = 'model) ->
default_model:'model ->
apply_action:
(inject:('action -> unit Effect.t) ->
schedule_event:(unit Effect.t -> unit) ->
'result ->
'model ->
'action ->
'model) ->
f:
('model Value.t ->
('action -> unit Effect.t) Value.t ->
'result Computation.t) ->
'result Computation.t
wrap
wraps a Computation (built using f
) and provides a model and injection function that the wrapped component can use. Especially of note is that the apply_action
for this outer-model has access to the result value of the Computation being wrapped.
val with_model_resetter :
'a Computation.t ->
('a * unit Effect.t) Computation.t
with_model_resetter
extends a computation with the ability to reset the state machine for that computation back to its default. This can be useful for e.g. clearing a form of all input values.
module Clock : sig ... end
Functions allowing for the creation of time-dependent computations in a testable way.
module Edge : sig ... end
All the functions in this module incorporate the concept of "edge-triggering", which is the terminology that we use to describe actions that occur when a value changes.
module Dynamic_scope : sig ... end
This module implements dynamic variable scoping. Once a dynamic variable is created, you can store values in it, and lookup those same values. A lookup will find the nearest-most grandparent set_within
call.
module Incr : sig ... end
module Let_syntax : sig ... end
This Let_syntax
module is basically just Value
.Let_syntax with the addition of the sub
function, which operates on Computations.
module Debug : sig ... end
module Private : sig ... end
module Arrow_deprecated : sig ... end