package bonsai

  1. Overview
  2. Docs
Legend:
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
type ('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          |                  |
module Value : sig ... end
module Computation : sig ... end
module Effect = Ui_effect
module For_open : sig ... end
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.

module Computation_status : sig ... end
val state : ?reset:('model -> 'model) -> (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 : ?reset:('model option -> 'model option) -> ?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 toggle : default_model:bool -> (bool * unit Effect.t) Computation.t

A bool-state which starts at default_model and flips whenever the returned effect is scheduled.

module Toggle : sig ... end
val toggle' : default_model:bool -> Toggle.t Computation.t

Like toggle, but also gives a handle to set the state directly

val state_machine0 : ?reset: (inject:('action -> unit Effect.t) -> schedule_event:(unit Effect.t -> unit) -> 'model -> 'model) -> (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 state_machine1 : (module Model with type t = 'model) -> (module Action with type t = 'action) -> ?reset: (inject:('action -> unit Effect.t) -> schedule_event:(unit Effect.t -> unit) -> 'model -> 'model) -> default_model:'model -> apply_action: (inject:('action -> unit Effect.t) -> schedule_event:(unit Effect.t -> unit) -> 'input Computation_status.t -> '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. 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) -> (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 : (module Model with type t = 'model) -> (module Action with type t = 'action) -> ?reset: (inject:('action -> 'return Effect.t) -> schedule_event:(unit Effect.t -> unit) -> 'model -> 'model) -> default_model:'model -> recv: (schedule_event:(unit Effect.t -> unit) -> 'input Computation_status.t -> '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 renamed recv, 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 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 freeze : (module Model with type t = 'a) -> 'a Value.t -> 'a Computation.t

freeze 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 scope_model : ('a, _) comparator -> on:'a Value.t -> 'b Computation.t -> 'b Computation.t

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 : (module Model with type t = 'b) -> 'a Value.t -> f:('a -> 'b option) -> 'b option Computation.t

most_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 : (module Model with type t = 'a) -> 'a Value.t -> condition:('a -> bool) -> 'a option Computation.t

most_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 : (module Model with type t = 'a) -> 'a Value.t -> 'a option Computation.t

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

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

Like 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.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 : ?reset: (inject:('action -> unit Effect.t) -> schedule_event:(unit Effect.t -> unit) -> 'model -> 'model) -> (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 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.t

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

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

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 Memo : sig ... end

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.

module Effect_throttling : sig ... end
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 Expert : sig ... end
module Map : sig ... end

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.

module Arrow_deprecated : sig ... end
module Stable : sig ... end
OCaml

Innovation. Community. Security.