BonsaiSourceBonsai documentation can be found in proc_intf.ml.
The Bonsai API is currently in an intermediate state. It is transitioning from the "old" Proc API to the "new" Cont API. Currently the Proc API is the default and is included when you use include Bonsai/_web/_web. Current Bonsai documentation can be found in proc_intf.ml.
module Private_computation := Computationmodule Private_value := Valuetype ('k, 'cmp) comparator =
(module Bonsai__.Module_types.Comparator
with type comparator_witness = 'cmp
and type t = 'k)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 | |
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.tCreates a Computation.t that provides a constant value.
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.
Lifts a regular OCaml function into one that takes a Value as input, and produces a Computation as output.
val state :
?reset:('model -> 'model) ->
?sexp_of_model:('model -> Core.Sexp.t) ->
?equal:('model -> 'model -> bool) ->
'model ->
('model * ('model -> unit Effect.t)) Computation.tA 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 :
?reset:('model option -> 'model option) ->
?default_model:'model ->
?sexp_of_model:('model -> Core.Sexp.t) ->
?equal:('model -> 'model -> bool) ->
unit ->
('model option * ('model option -> unit Effect.t)) Computation.tSimilar to state, but stores an option of the model instead. default_model is optional and defaults to None.
A bool-state which starts at default_model and flips whenever the returned effect is scheduled.
Like toggle, but also gives a handle to set the state directly
val state_machine0 :
?reset:('action Apply_action_context.t -> 'model -> 'model) ->
?sexp_of_model:('model -> Core.Sexp.t) ->
?sexp_of_action:('action -> Core.Sexp.t) ->
?equal:('model -> 'model -> bool) ->
default_model:'model ->
apply_action:('action Apply_action_context.t -> 'model -> 'action -> 'model) ->
unit ->
('model * ('action -> unit Effect.t)) Computation.tA constructor for Computation.t that models a simple state machine. 'model describes the states in the state machine, while '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 'action Apply_action_context.t to be unused)
val state_machine1 :
?sexp_of_action:('action -> Core.Sexp.t) ->
?reset:('action Apply_action_context.t -> 'model -> 'model) ->
?sexp_of_model:('model -> Core.Sexp.t) ->
?equal:('model -> 'model -> bool) ->
default_model:'model ->
apply_action:
('action Apply_action_context.t ->
'input Computation_status.t ->
'model ->
'action ->
'model) ->
'input Value.t ->
('model * ('action -> unit Effect.t)) Computation.tThe same as state_machine0, but apply_action also takes an input from a Value.t. The input has type 'input Computation_status.t instead of plain 'input to account for the possibility that an action gets sent while the state machine is inactive.
val actor0 :
?reset:
(inject:('action -> 'return Effect.t) ->
schedule_event:(unit Effect.t -> unit) ->
'model ->
'model) ->
?sexp_of_model:('model -> Core.Sexp.t) ->
?sexp_of_action:('action -> Core.Sexp.t) ->
?equal:('model -> 'model -> bool) ->
default_model:'model ->
recv:
(inject:('action -> 'return Effect.t) ->
schedule_event:(unit Effect.t -> unit) ->
'model ->
'action ->
'model * 'return) ->
unit ->
('model * ('action -> 'return Effect.t)) Computation.tIdentical to actor1 but it takes 0 inputs instead of 1.
val actor1 :
?sexp_of_action:('action -> Core.Sexp.t) ->
?reset:
(inject:('action -> 'return Effect.t) ->
schedule_event:(unit Effect.t -> unit) ->
'model ->
'model) ->
?sexp_of_model:('model -> Core.Sexp.t) ->
?equal:('model -> 'model -> bool) ->
default_model:'model ->
recv:
(inject:('action -> 'return Effect.t) ->
schedule_event:(unit Effect.t -> unit) ->
'input Computation_status.t ->
'model ->
'action ->
'model * 'return) ->
'input Value.t ->
('model * ('action -> 'return Effect.t)) Computation.tactor1 is very similar to state_machine1, with two major exceptions:
apply-action function for state-machine is renamed recv, and it returns a "response", in addition to a new model.Because the semantics of this function feel like an actor system, we've decided to name the function accordingly.
val narrow :
('a * ('input_action -> unit Effect.t)) Value.t ->
get:('a -> 'b) ->
set:('a -> 'output_action -> 'input_action) ->
('b * ('output_action -> unit Effect.t)) Computation.tGiven a value containing the current state (like from a Bonsai.state or Bonsai.state_machine), narrow gives you access to a subset of the state and a setter for the subset of that type.
For example, you could use narrow a state containing a record to the value and injection function for a single field.
val narrow_via_field :
('a * ('a -> unit Effect.t)) Value.t ->
('a, 'b) Core.Field.t ->
('b * ('b -> unit Effect.t)) Computation.tLike narrow, but get and set are implemented in terms of the given field.
val of_module0 :
?sexp_of_model:('m -> Core.Sexp.t) ->
?equal:('m -> 'm -> bool) ->
(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.tGiven 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 :
?sexp_of_model:('m -> Core.Sexp.t) ->
(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) ->
?equal:('m -> 'm -> bool) ->
default_model:'m ->
'i Value.t ->
'r Computation.tThe 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 :
?sexp_of_model:('m -> Core.Sexp.t) ->
(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) ->
?equal:('m -> 'm -> bool) ->
default_model:'m ->
'i1 Value.t ->
'i2 Value.t ->
'r Computation.tThe same as of_module1 but with two inputs.
val freeze :
?sexp_of_model:('a -> Core.Sexp.t) ->
?equal:('a -> 'a -> bool) ->
'a Value.t ->
'a Computation.tfreeze takes a Value.t and returns a computation whose output is frozen to be the first value that passed through the input.
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 fix :
'input Value.t ->
f:
(recurse:('input Value.t -> 'result Computation.t) ->
'input Value.t ->
'result Computation.t) ->
'result Computation.tA fixed-point combinator for bonsai components. This is used to build recursive components like so:
let my_recursive_component ~some_input =
Bonsai.fix some_input ~f:(fun ~recurse some_input ->
(* call [recurse] to instantiate a nested instance of the component *)
)val fix2 :
'a Value.t ->
'b Value.t ->
f:
(recurse:('a Value.t -> 'b Value.t -> 'result Computation.t) ->
'a Value.t ->
'b Value.t ->
'result Computation.t) ->
'result Computation.tLike fix, but for two arguments instead of just one.
scope_model allows you to have a different model for the provided computation, keyed by some other value.
Suppose for example, that you had a form for editing details about a person. This form should have different state for each person. You could use scope_model, where the ~on parameter is set to a user-id, and now when that value changes, the model for the other computation is set to the model for that particular user.
scope_model also impacts lifecycle events; when on changes value, edge triggers like on_activate and on_deactivate will run
val most_recent_some :
?sexp_of_model:('b -> Core.Sexp.t) ->
equal:('b -> 'b -> bool) ->
'a Value.t ->
f:('a -> 'b option) ->
'b option Computation.tmost_recent_some returns a value containing the most recent output of f for which it returned Some. If the input value has never contained a valid value, then the result is None.
val most_recent_value_satisfying :
?sexp_of_model:('a -> Core.Sexp.t) ->
equal:('a -> 'a -> bool) ->
'a Value.t ->
condition:('a -> bool) ->
'a option Computation.tmost_recent_value_satisfying returns a value containing the most recent input value for which condition returns true. If the input value has never contained a valid value, then the result is None.
val previous_value :
?sexp_of_model:('a -> Core.Sexp.t) ->
equal:('a -> 'a -> bool) ->
'a Value.t ->
'a option Computation.tprevious_value returns the previous contents of the input value if it just changed, or the current contents of the value if it did not just change. Initially starts out as None.
Any values the input takes on while the output is inactive are ignored; any changes to the input are assumed to have occurred exactly when the component was re-activated.
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.tassoc 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 assoc_set :
('key, 'cmp) comparator ->
('key, 'cmp) Core.Set.t Value.t ->
f:('key Value.t -> 'result Computation.t) ->
('key, 'result, 'cmp) Core.Map.t Computation.tLike assoc except that the input value is a Set instead of a Map.
val assoc_list :
('key, _) comparator ->
'a list Value.t ->
get_key:('a -> 'key) ->
f:('key Value.t -> 'a Value.t -> 'b Computation.t) ->
[ `Duplicate_key of 'key | `Ok of 'b list ] Computation.tLike assoc except that the input value is a list instead of a Map. The output list is in the same order as the input list.
This function performs O(n log(n)) work (where n is the length of the list) any time that anything in the input list changes, so it may be quite slow with large lists.
val enum :
(module Enum with type t = 'k) ->
match_:'k Value.t ->
with_:('k -> 'a Computation.t) ->
'a Computation.tenum 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 :
?reset:('action Apply_action_context.t -> 'model -> 'model) ->
?sexp_of_model:('model -> Core.Sexp.t) ->
?equal:('model -> 'model -> bool) ->
default_model:'model ->
apply_action:
('action Apply_action_context.t -> 'result -> 'model -> 'action -> 'model) ->
f:
('model Value.t ->
('action -> unit Effect.t) Value.t ->
'result Computation.t) ->
unit ->
'result Computation.twrap 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.
with_model_resetter extends a computation with the ability to reset all of the models for components contained in that computation. The default behavior for a stateful component is to have its model set to the value provided by default_model, though this behavior is overridable on a component-by-component basis by providing a value for the optional reset argument on stateful components.
val with_model_resetter' :
(reset:unit Effect.t Value.t -> 'a Computation.t) ->
'a Computation.tlike with_model_resetter, but makes the resetting effect available to the computation being wrapped.
yoink is a function that takes a bonsai value and produces a computation producing an effect which fetches the current value out of the input. This can be useful inside of let%bind.Effect chains, where a value that you've closed over is stale and you want to witness a value after it's been changed by a previous effect.
The 'a Computation_state.t returned by the effect means that if the value was inactive at the time it got yoinked, then the effect will be unable to retrieve it.
val sub :
?here:Core.Source_code_position.t ->
'a Computation.t ->
f:('a Value.t -> 'b Computation.t) ->
'b Computation.tsub instantiates a computation and provides a reference to its results to f in the form of a Value.t. The main way to use this function is via the let%sub syntax extension. ?here is used by the Bonsai debugger to tie visualizations to precise source locations.
Functions allowing for the creation of time-dependent computations in a testable way.
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.
The Memo module can be used to share a computation between multiple components, meaning that if the shared computation is stateful, then the users of that computation will see the same state.
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 parent "unreverted" set call where a "set" can be "reverted" with set''s revert.
This Let_syntax module is basically just Value.Let_syntax with the addition of the sub function, which operates on Computations.
Analog to Incr_map functions in Bonsai. In general, you should prefer to use Bonsai.assoc where possible. For functions that are particularly easy to implement in terms of assoc, the function is stubbed with a `Use_assoc value instead. We also skip wrapping the prime versions of Incr_map functions, since they more easily allow Incr.bind, which we want to make sure is used only when absolutely necessary.