bonsai

A library for building dynamic webapps, using Js_of_ocaml
IN THIS PACKAGE
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 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 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 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.

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