Bonsai_procmodule Cont := BonsaiBonsai_proc is a legacy Bonsai API based on Value.t and Computation.t. New apps should use the Bonsai library instead.
module type Enum = Bonsai_private_base.Module_types.EnumThe 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 ... endmodule Computation : sig ... endmodule Effect = Ui_effectmodule For_open : sig ... endmodule Var : sig ... endval read : 'a Value.t -> 'a Computation.tConverts 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 and 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.tval const : here:lexing_position -> 'a -> 'a Computation.tCreates a Computation.t that provides a constant value.
val path_id : here:lexing_position -> unit -> string Computation.tRetrieves 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 : here:lexing_position -> ('a -> 'b) -> 'a Value.t -> 'b Computation.tLifts a regular OCaml function into one that takes a Value as input, and produces a Computation as output.
module Computation_status : sig ... endval state :
here:lexing_position ->
?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 :
here:lexing_position ->
?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.
val state' :
here:lexing_position ->
?reset:('model -> 'model) ->
?sexp_of_model:('model -> Core.Sexp.t) ->
?equal:('model -> 'model -> bool) ->
'model ->
('model * (('model -> 'model) -> unit Effect.t)) Computation.tSimilar to state, but the `set` function takes a function that calculates the new state from the previous state.
val toggle :
here:lexing_position ->
default_model:bool ->
unit ->
(bool * unit Effect.t) Computation.tA bool-state which starts at default_model and flips whenever the returned effect is scheduled.
module Toggle : sig ... endval toggle' :
here:lexing_position ->
default_model:bool ->
unit ->
Toggle.t Computation.tLike toggle, but also gives a handle to set the state directly
module Apply_action_context : sig ... endval state_machine :
here:lexing_position ->
?reset:(('action, unit) 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, unit) 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_machine_with_input :
here:lexing_position ->
?sexp_of_action:('action -> Core.Sexp.t) ->
?reset:(('action, unit) Apply_action_context.t -> 'model -> 'model) ->
?sexp_of_model:('model -> Core.Sexp.t) ->
?equal:('model -> 'model -> bool) ->
default_model:'model ->
apply_action:
(('action, unit) Apply_action_context.t ->
'input Computation_status.t ->
'model ->
'action ->
'model) ->
'input Value.t ->
('model * ('action -> unit Effect.t)) Computation.tThe same as state_machine, 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 actor :
here:lexing_position ->
?reset:(('action, 'return) 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 ->
recv:
(('action, 'return) Apply_action_context.t ->
'model ->
'action ->
'model * 'return) ->
unit ->
('model * ('action -> 'return Effect.t)) Computation.tIdentical to actor_with_input but it takes 0 inputs instead of 1.
val actor_with_input :
here:lexing_position ->
?sexp_of_action:('action -> Core.Sexp.t) ->
?reset:(('action, 'return) Apply_action_context.t -> 'model -> 'model) ->
?sexp_of_model:('model -> Core.Sexp.t) ->
?equal:('model -> 'model -> bool) ->
default_model:'model ->
recv:
(('action, 'return) Apply_action_context.t ->
'input Computation_status.t ->
'model ->
'action ->
'model * 'return) ->
'input Value.t ->
('model * ('action -> 'return Effect.t)) Computation.tactor_with_input is very similar to state_machine_with_input, 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 :
here:lexing_position ->
('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 :
here:lexing_position ->
('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_module :
?sexp_of_model:('m -> Core.Sexp.t) ->
?equal:('m -> 'm -> bool) ->
(module Bonsai_proc__.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_module will create a Computation that produces values of that module's Result.t type.
val of_module_with_input :
here:lexing_position ->
?sexp_of_model:('m -> Core.Sexp.t) ->
(module Bonsai_proc__.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_module, 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_module_with_input (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 :
here:lexing_position ->
?sexp_of_model:('m -> Core.Sexp.t) ->
(module Bonsai_proc__.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_module_with_input but with two inputs.
val freeze :
here:lexing_position ->
?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.
val lazy_ :
here:lexing_position ->
'a Computation.t Core.Lazy.t ->
'a Computation.tlazy_ c produces a computation that only forces c when necessary. This can be used to delay large computations that might not initially be useful.
For recursive computations, see fix.
val fix :
here:lexing_position ->
'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 :
here:lexing_position ->
'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.
val scope_model :
here:lexing_position ->
('a, _) Core.Comparator.Module.t ->
on:'a Value.t ->
'b Computation.t ->
'b Computation.tscope_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 :
here:lexing_position ->
?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 :
here:lexing_position ->
?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 :
here:lexing_position ->
?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 :
here:lexing_position ->
('key, 'cmp) Core.Comparator.Module.t ->
('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 :
here:lexing_position ->
('key, 'cmp) Core.Comparator.Module.t ->
('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 :
here:lexing_position ->
('key, _) Core.Comparator.Module.t ->
'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 :
here:lexing_position ->
(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 :
here:lexing_position ->
?reset:(('action, unit) Apply_action_context.t -> 'model -> 'model) ->
?sexp_of_model:('model -> Core.Sexp.t) ->
?equal:('model -> 'model -> bool) ->
default_model:'model ->
apply_action:
(('action, unit) Apply_action_context.t ->
'result Computation_status.t ->
'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. 'result is wrapped in a Computation_status.t so that you are able to reset the underlying model even if the computation is inactive
val with_model_resetter :
here:lexing_position ->
'a Computation.t ->
('a * unit Effect.t) Computation.twith_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' :
here:lexing_position ->
(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.
val yoink :
here:lexing_position ->
'a Value.t ->
'a Computation_status.t Effect.t Computation.tyoink 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:lexing_position ->
'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:[%call_pos] is used by the Bonsai debugger to tie visualizations to precise source locations.
module Clock : sig ... endFunctions allowing for the creation of time-dependent computations in a testable way.
module Edge : sig ... endAll 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 Memo : sig ... endThe 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.
module Effect_throttling : sig ... endmodule Dynamic_scope : sig ... endThis 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.
module Incr : sig ... endmodule Let_syntax : sig ... endThis Let_syntax module is basically just Value.Let_syntax with the addition of the sub function, which operates on Computations.
module Time_source = Bonsai_private_base.Import.Time_sourcemodule Expert : sig ... endmodule Debug : sig ... endval path :
here:lexing_position ->
unit ->
Bonsai_private_base.Path.t Computation.tAnalog 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.
module Private = Bonsai.Privatemodule Bonsai : sig ... end