1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192open!Coreopen!ImportmoduletypeS=sigmoduletypeEnum=Module_types.Enum(** 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:
{v
| 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 | |
v} *)moduleValue:sig(** A value of type ['a Value.t] represents a value that may change during the
lifetime of the program. For those familiar with the [Incremental] library, this
type is conceptually very similar to [Incr.t]. The main method by which you
acquire values of type [Value.t] is by using the [let%sub] syntax extension.
{[
val c : int Computation.t
let%sub x = c in
(* [x] has type [int Value.t] here *)
]}
In the example above, we run a computation [c] and store the result of that
computation in [x] which has type [Value.t].
[Value.t] is an applicative, which means that you can combine multiple [Value]s
into one by using [Let_syntax]:
{[
val a : int Value.t
val b : int Value.t
let open Let_syntax in
let%map a = a and b = b in
a + b
]} *)type'atincludeApplicative.Swithtype'at:='atincludeMapnwithtype'at:='at(** A [Value.t] transformed by [cutoff] will only trigger changes on its dependents
when the equality of the contained value has changed.
Immediate nesting of cutoff nodes are combined into a single cutoff node whose
equality function is true when any of the composed nodes is true and is false when
all of the composed nodes are false. They're "or'ed together". *)valcutoff:here:[%call_pos]->'at->equal:('a->'a->bool)->'at(** flips the option position in a ['a Value.t option] into an ['a option Value.t].
It's useful for optional args that take values. *)valtranspose_opt:'atoption->'aoptiontendmoduleComputation:sig(** A value of type ['a Computation.t] represents a computation which produces a value
that may change during the lifetime of a program, and the value may be influenced
by the internal state of that computation.
The same ['a Computation.t] can be used in multiple places in a program, and these
uses will {e not} share the same state, nor will they share the work performed by
the computation.
In this normal OCaml code, if we see the same function being called multiple
times:
{[
let a = f () in
let b = f () in
a + b
]}
You would not be surprised to know that if [f] has side-effects (maybe printing to
the console), then those side-effects happen twice because [f] was called twice.
Similarly, if we wrote the code this way:
{[
let a = f () in
let b = a in
a + b
]}
You would (correctly) expect that the side-effect only happens once, when
computing [a]. In these examples, the {e code} [f ()] is analogous to
[_ Computation.t]. If you want to have two separate values whose computations
maintain separate state, you would use two instances of "let%sub" to bind them
separately:
{[
val some_computation : int Computation.t
val add : int Value.t -> int Value.t -> int Computation.t
let open Let_syntax in
let%sub a = some_computation in
let%sub b = some_computation in
add a b
]}
Here, [a] and [b] can take on different values depending on the states of the
computations that produce them.
However, if you want to use just one value in multiple places, only use [let%sub]
once:
{[
let open Let_syntax in
let%sub a = some_computation in
let b = a in
add a b
]}
Here, [a] and [b] always take on the same value. *)type'atincludeApplicative.Swithtype'at:='at(** Similar to [all] which pulls the computation outside of a list, [all_map] does the
same, but with the data in a map. This can be a useful replacement for [assoc] in
scenarios where the map is a constant size. *)valall_map:here:[%call_pos]->('k,'vt,'cmp)Map.t->('k,'v,'cmp)Map.tt(** The analog of [List.reduce_balanced] for computations, but with [f] operating on
values instead of the computations themselves *)valreduce_balanced:'atlist->f:('aValue.t->'aValue.t->'at)->'atoptionvalfold_right:'atlist->f:('aValue.t->'accValue.t->'acct)->init:'accValue.t->'acctmoduleLet_syntax:sigvalreturn:here:[%call_pos]->'a->'atincludeImport.Applicative.Applicative_infixwithtype'at:='atmoduleLet_syntax:sigvalreturn:here:[%call_pos]->'a->'atvalmap:here:[%call_pos]->'at->f:('a->'b)->'btvalboth:here:[%call_pos]->'at->'bt->('a*'b)tincludeMapnwithtype'at:='atendendincludeMapnwithtype'at:='atendmoduleEffect=Ui_effectmoduleFor_open:sigmoduleComputation=ComputationmoduleEffect=EffectmoduleValue=ValueendmoduleVar:sig(** A [Var.t] is the primary method for making data obtained outside of Bonsai (maybe
via an RPC) accessible inside a Bonsai application. *)type'at(** Creates a new [Var.t] with an initial value. *)valcreate:'a->'at(** Updates the value inside of [t]. [f] is given the previous value of [t] so that
you can reuse parts of the value if applicable *)valupdate:'at->f:('a->'a)->unit(** Sets the value inside of [t]. *)valset:here:[%call_pos]->'at->'a->unit(** Gets the value inside of [t]. *)valget:'at->'a(** Provides read-only access to [t] by producing a {!Value.t} which is used inside of
a Bonsai computation. *)valvalue:here:[%call_pos]->'at->'aValue.t(** Retrieves the underlying ['a t] Ui_incr.t var. *)valincr_var:'at->'aUi_incr.Var.tend(** 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 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.t
]} *)valread:'aValue.t->'aComputation.t(** Creates a [Computation.t] that provides a constant value. *)valconst:here:[%call_pos]->'a->'aComputation.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. *)valpath_id:here:[%call_pos]->unit->stringComputation.t(** Lifts a regular OCaml function into one that takes a Value as input, and produces a
Computation as output. *)valpure:here:[%call_pos]->('a->'b)->'aValue.t->'bComputation.tmoduleComputation_status:sig(** Indicates whether a value is available, which depends on whether the computation
in which it is computed is active or not. Most of the time values of this type are
[Active], since it is unusual to interact with inactive computations.
A computation is considered inactive if it resides in the inactive arm of a
[match%sub] or in a removed entry of a [Bonsai.assoc]. *)type'inputt=|Activeof'input|Inactive[@@derivingsexp_of]end(** 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. *)valstate:here:[%call_pos]->?reset:('model->'model)(** to learn more about [reset], read the docs on [with_model_resetter] *)->?sexp_of_model:('model->Sexp.t)->?equal:('model->'model->bool)->'model->('model*('model->unitEffect.t))Computation.t(** Similar to [state], but stores an option of the model instead. [default_model] is
optional and defaults to [None]. *)valstate_opt:here:[%call_pos]->?reset:('modeloption->'modeloption)->?default_model:'model->?sexp_of_model:('model->Sexp.t)->?equal:('model->'model->bool)->unit->('modeloption*('modeloption->unitEffect.t))Computation.t(** Similar to [state], but the `set` function takes a function that calculates the new
state from the previous state. *)valstate':here:[%call_pos]->?reset:('model->'model)(** to learn more about [reset], read the docs on [with_model_resetter] *)->?sexp_of_model:('model->Sexp.t)->?equal:('model->'model->bool)->'model->('model*(('model->'model)->unitEffect.t))Computation.t(** A bool-state which starts at [default_model] and flips whenever the returned effect
is scheduled. *)valtoggle:here:[%call_pos]->default_model:bool->unit->(bool*unitEffect.t)Computation.tmoduleToggle:sigtypet={state:bool;set_state:bool->unitEffect.t;toggle:unitEffect.t}end(** Like [toggle], but also gives a handle to set the state directly *)valtoggle':here:[%call_pos]->default_model:bool->unit->Toggle.tComputation.tmoduleApply_action_context:sigtype('action,'response)t=('action,'response)Apply_action_context.tvalinject:('action,'response)t->'action->'responseEffect.tvalschedule_event:_t->unitEffect.t->unitvaltime_source:_t->Time_source.tend(** A 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) *)valstate_machine:here:[%call_pos]->?reset:(('action,unit)Apply_action_context.t->'model->'model)(** to learn more about [reset], read the docs on [with_model_resetter] *)->?sexp_of_model:('model->Sexp.t)->?sexp_of_action:('action->Sexp.t)->?equal:('model->'model->bool)->default_model:'model->apply_action:(('action,unit)Apply_action_context.t->'model->'action->'model)->unit->('model*('action->unitEffect.t))Computation.t(** The 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. *)valstate_machine_with_input:here:[%call_pos]->?sexp_of_action:('action->Sexp.t)->?reset:(('action,unit)Apply_action_context.t->'model->'model)(** to learn more about [reset], read the docs on [with_model_resetter] *)->?sexp_of_model:('model->Sexp.t)->?equal:('model->'model->bool)->default_model:'model->apply_action:(('action,unit)Apply_action_context.t->'inputComputation_status.t->'model->'action->'model)->'inputValue.t->('model*('action->unitEffect.t))Computation.t(** Identical to [actor_with_input] but it takes 0 inputs instead of 1. *)valactor:here:[%call_pos]->?reset:(('action,'return)Apply_action_context.t->'model->'model)(** to learn more about [reset], read the docs on [with_model_resetter] *)->?sexp_of_model:('model->Sexp.t)->?sexp_of_action:('action->Sexp.t)->?equal:('model->'model->bool)->default_model:'model->recv:(('action,'return)Apply_action_context.t->'model->'action->'model*'return)->unit->('model*('action->'returnEffect.t))Computation.t(** [actor_with_input] is very similar to [state_machine_with_input], 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. *)valactor_with_input:here:[%call_pos]->?sexp_of_action:('action->Sexp.t)->?reset:(('action,'return)Apply_action_context.t->'model->'model)(** to learn more about [reset], read the docs on [with_model_resetter] *)->?sexp_of_model:('model->Sexp.t)->?equal:('model->'model->bool)->default_model:'model->recv:(('action,'return)Apply_action_context.t->'inputComputation_status.t->'model->'action->'model*'return)->'inputValue.t->('model*('action->'returnEffect.t))Computation.t(** Given 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. *)valnarrow:here:[%call_pos]->('a*('input_action->unitEffect.t))Value.t->get:('a->'b)->set:('a->'output_action->'input_action)->('b*('output_action->unitEffect.t))Computation.t(** Like [narrow], but [get] and [set] are implemented in terms of the given field. *)valnarrow_via_field:here:[%call_pos]->('a*('a->unitEffect.t))Value.t->('a,'b)Field.t->('b*('b->unitEffect.t))Computation.t(** Given 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. *)valof_module:?sexp_of_model:('m->Sexp.t)->?equal:('m->'m->bool)->(unit,'m,'a,'r)component_s->default_model:'m->'rComputation.t(** The 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. *)valof_module_with_input:here:[%call_pos]->?sexp_of_model:('m->Sexp.t)->('i,'m,'a,'r)component_s->?equal:('m->'m->bool)->default_model:'m->'iValue.t->'rComputation.t(** The same as {!of_module_with_input} but with two inputs. *)valof_module2:here:[%call_pos]->?sexp_of_model:('m->Sexp.t)->('i1*'i2,'m,'a,'r)component_s->?equal:('m->'m->bool)->default_model:'m->'i1Value.t->'i2Value.t->'rComputation.t(** [freeze] takes a Value.t and returns a computation whose output is frozen to be the
first value that passed through the input. *)valfreeze:here:[%call_pos]->?sexp_of_model:('a->Sexp.t)->?equal:('a->'a->bool)->'aValue.t->'aComputation.t(** [lazy_ 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]. *)vallazy_:here:[%call_pos]->'aComputation.tLazy.t->'aComputation.t(** A 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 *)
)
]} *)valfix:here:[%call_pos]->'inputValue.t->f:(recurse:('inputValue.t->'resultComputation.t)->'inputValue.t->'resultComputation.t)->'resultComputation.t(** Like [fix], but for two arguments instead of just one. *)valfix2:here:[%call_pos]->'aValue.t->'bValue.t->f:(recurse:('aValue.t->'bValue.t->'resultComputation.t)->'aValue.t->'bValue.t->'resultComputation.t)->'resultComputation.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 *)valscope_model:here:[%call_pos]->('a,_)Comparator.Module.t->on:'aValue.t->'bComputation.t->'bComputation.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]. *)valmost_recent_some:here:[%call_pos]->?sexp_of_model:('b->Sexp.t)->equal:('b->'b->bool)->'aValue.t->f:('a->'boption)->'boptionComputation.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]. *)valmost_recent_value_satisfying:here:[%call_pos]->?sexp_of_model:('a->Sexp.t)->equal:('a->'a->bool)->'aValue.t->condition:('a->bool)->'aoptionComputation.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. *)valprevious_value:here:[%call_pos]->?sexp_of_model:('a->Sexp.t)->equal:('a->'a->bool)->'aValue.t->'aoptionComputation.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. *)valassoc:here:[%call_pos]->('key,'cmp)Comparator.Module.t->('key,'data,'cmp)Map.tValue.t->f:('keyValue.t->'dataValue.t->'resultComputation.t)->('key,'result,'cmp)Map.tComputation.t(** Like [assoc] except that the input value is a Set instead of a Map. *)valassoc_set:here:[%call_pos]->('key,'cmp)Comparator.Module.t->('key,'cmp)Set.tValue.t->f:('keyValue.t->'resultComputation.t)->('key,'result,'cmp)Map.tComputation.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. *)valassoc_list:here:[%call_pos]->('key,_)Comparator.Module.t->'alistValue.t->get_key:('a->'key)->f:('keyValue.t->'aValue.t->'bComputation.t)->[`Duplicate_keyof'key|`Okof'blist]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. *)valenum:here:[%call_pos]->(moduleEnumwithtypet='k)->match_:'kValue.t->with_:('k->'aComputation.t)->'aComputation.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. ['result] is wrapped in a [Computation_status.t] so that
you are able to reset the underlying model even if the computation is inactive *)valwrap:here:[%call_pos]->?reset:(('action,unit)Apply_action_context.t->'model->'model)->?sexp_of_model:('model->Sexp.t)->?equal:('model->'model->bool)->default_model:'model->apply_action:(('action,unit)Apply_action_context.t->'resultComputation_status.t->'model->'action->'model)->f:('modelValue.t->('action->unitEffect.t)Value.t->'resultComputation.t)->unit->'resultComputation.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. *)valwith_model_resetter:here:[%call_pos]->'aComputation.t->('a*unitEffect.t)Computation.t(** like [with_model_resetter], but makes the resetting effect available to the
computation being wrapped. *)valwith_model_resetter':here:[%call_pos]->(reset:unitEffect.tValue.t->'aComputation.t)->'aComputation.t(** [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. *)valyoink:here:[%call_pos]->'aValue.t->'aComputation_status.tEffect.tComputation.t(** [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:[%call_pos]] is used by the Bonsai debugger to tie
visualizations to precise source locations. *)valsub:here:[%call_pos]->'aComputation.t->f:('aValue.t->'bComputation.t)->'bComputation.tmoduleClock:sig(** Functions allowing for the creation of time-dependent computations in a testable
way. *)(** The current time, updated at [tick_every] intervals. *)valapprox_now:here:[%call_pos]->tick_every:Time_ns.Span.t->unit->Time_ns.tComputation.tmoduleBefore_or_after:sigtypet=Ui_incr.Before_or_after.t=|Before|After[@@derivingsexp,equal]end(** Mirrors [Incr.Clock.at], which changes from [Before] to [After] at the specified
time. *)valat:here:[%call_pos]->Time_ns.tValue.t->Before_or_after.tComputation.t(** {v
An event passed to [every] is scheduled on an interval determined by
the time-span argument.
[when_to_start_next_effect] has the following behavior
| `Wait_period_after_previous_effect_starts_blocking -> If the previous effect takes longer than [period], we wait until it finishes before starting the next effect.
| `Wait_period_after_previous_effect_finishes_blocking -> The effect will always be executed [period] after the previous effect finishes.
| `Every_multiple_of_period_non_blocking -> Executes the effect at a regular interval.
| `Every_multiple_of_period_blocking -> Same as `Every_multiple_of_second, but skips a beat if the previous effect is still running.
v} *)valevery:here:[%call_pos]->when_to_start_next_effect:[<`Wait_period_after_previous_effect_starts_blocking|`Wait_period_after_previous_effect_finishes_blocking|`Every_multiple_of_period_non_blocking|`Every_multiple_of_period_blocking]->?trigger_on_activate:bool->Time_ns.Span.tValue.t->unitEffect.tValue.t->unitComputation.t(** An effect for fetching the current time. *)valget_current_time:here:[%call_pos]->unit->Time_ns.tEffect.tComputation.t(** The function in this computation produces an effect that completes after the
specified amount of time. *)valsleep:here:[%call_pos]->unit->(Time_ns.Span.t->unitEffect.t)Computation.t(** Like [sleep], but waits until a specific time, rather than a time relative to now. *)valuntil:here:[%call_pos]->unit->(Time_ns.t->unitEffect.t)Computation.tmoduleExpert:sig(** The current time, update as frequently as possible. *)valnow:here:[%call_pos]->unit->Time_ns.tComputation.tendendmoduleEdge:sig(** 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. *)(** When given a value and a callback, [on_change] and [on_change'] will watch the
input variable and call the callback whenever the value changes.
[callback] is also called when the component is initialized, passing in the first
'a value that gets witnessed.
These functions do not wait for previous calls to [callback] to complete before
calling it again. *)valon_change:here:[%call_pos]->?sexp_of_model:('a->Sexp.t)->?trigger:[`Before_display|`After_display]->equal:('a->'a->bool)->'aValue.t->callback:('a->unitEffect.t)Value.t->unitComputation.t(** The same as [on_change], but the callback function gets access to the previous
value that was witnessed. *)valon_change':here:[%call_pos]->?sexp_of_model:('a->Sexp.t)->?trigger:[`Before_display|`After_display]->equal:('a->'a->bool)->'aValue.t->callback:('aoption->'a->unitEffect.t)Value.t->unitComputation.t(** [lifecycle] is a way to detect when a computation becomes active, inactive, or an
event is triggered after every rendering (roughly 60x / second). By depending on
this function (with let%sub), you can install events that are scheduled on either
case.
When used, the events are scheduled in this order:
- All deactivations
- All activations
- All "after-display"s
and an "after-display" won't occur before an activation, or after a deactivation
for a given computation. *)vallifecycle:here:[%call_pos]->?on_activate:unitEffect.tValue.t->?on_deactivate:unitEffect.tValue.t->?before_display:unitEffect.tValue.t->?after_display:unitEffect.tValue.t->unit->unitComputation.t(** Like [lifecycle], but the events are optional values. If the event value is None
when the action occurs, nothing will happen *)vallifecycle':here:[%call_pos]->?on_activate:unitEffect.toptionValue.t->?on_deactivate:unitEffect.toptionValue.t->?before_display:unitEffect.toptionValue.t->?after_display:unitEffect.toptionValue.t->unit->unitComputation.t(** [before_display] and [before_display'] are lower-level functions that can be used
to register an event to occur once-per-frame (before the vdom is mounted) *)valbefore_display:here:[%call_pos]->unitEffect.tValue.t->unitComputation.tvalbefore_display':here:[%call_pos]->unitEffect.toptionValue.t->unitComputation.t(** [after_display] and [after_display'] are lower-level functions that can be used to
register an event to occur once-per-frame (after each render). *)valafter_display:here:[%call_pos]->unitEffect.tValue.t->unitComputation.tvalafter_display':here:[%call_pos]->unitEffect.toptionValue.t->unitComputation.t(** [wait_after_display] is an effect that will complete after the next frame. *)valwait_after_display:here:[%call_pos]->unit->unitEffect.tComputation.tmodulePoll:sigmoduleStarting:sigtype('o,'r)t(** [empty] is an option to pass to the polling functions that changes its return
type to be ['o option Computation.t] and starting value is [None] *)valempty:('o,'ooption)t(** [initial x] is an option to pass to the polling functions that changes its
return type to be ['o Computation.t] and the starting value is [x] *)valinitial:'o->('o,'o)tend(** This function runs an effect every time that the input value changes, returning
the most recent result as its computation.
The [Starting.t] argument controls the type of the result, and depending on the
value, will either return an optional value [Option.None] or a default value
['o] in the time in between the computation starting and the first result coming
back from the effect. *)valeffect_on_change:here:[%call_pos]->?sexp_of_input:('a->Sexp.t)->?sexp_of_result:('o->Sexp.t)->equal_input:('a->'a->bool)->?equal_result:('o->'o->bool)->('o,'r)Starting.t->'aValue.t->effect:('a->'oEffect.t)Value.t->'rComputation.tvalmanual_refresh:here:[%call_pos]->?sexp_of_model:('o->Sexp.t)->?equal:('o->'o->bool)->('o,'r)Starting.t->effect:'oEffect.tValue.t->('r*unitEffect.t)Computation.tendendmoduleMemo:sig(** 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.
The way that [Memo] differs from just using [let%sub] on a computation and then
passing the resulting [Value.t] down to its children is twofold:
- The shared computation is not made active until it's actually requested by
another component
- Knowledge of any inputs to component are be deferred to "lookup time", when
components request an instance of the component.
Shared computations are refcounted, so when the last user of a memoized component
deactivates, the shared component is deactivated as well. *)type('input,'result)t(** Creates a memo instance that can be used by calling [lookup] *)valcreate:here:[%call_pos]->('input,'cmp)Comparator.Module.t->f:('inputValue.t->'resultComputation.t)->('input,'result)tComputation.t(** Requests an instance of the shared computation for a given ['input] value. If an
instance doesn't already exist, it will request a new computation, which results
in [none] being returned for a brief period of time, after which it'll return a
[Some] containing the result of that computation *)vallookup:here:[%call_pos]->('input,'result)tValue.t->'inputValue.t->'resultoptionComputation.tendmoduleEffect_throttling:sigmodulePoll_result:sigtype'at=|Aborted(** [Aborted] indicates that the effect was aborted before it even started. If an
effect starts, then it should complete with some kind of result - [Effect]
does not support cancellation in general. *)|Finishedof'a(** [Finished x] indicates that an effect successfully completed with value x. *)[@@derivingsexp,equal](** Collapses values of type ['a Or_error.t t] a plain Or_error.t, where the Aborted
case is transformed into an error.
The [tag_s] parameter can be used to add additional info to the error. *)valcollapse_to_or_error:?tag_s:Sexp.tlazy_t->'aOr_error.tt->'aOr_error.t(** Like [collapse_to_or_error], but transforms a function that returns an
['a Or_error.t t] instead of just the value. *)valcollapse_fun_to_or_error:?sexp_of_input:('a->Sexp.t)->('a->'bOr_error.ttEffect.t)->('a->'bOr_error.tEffect.t)end(** Transforms an input effect into a new effect that enforces that invariant that at
most one instance of the effect is running at once. Attempting to run the effect
while a previous run is still ongoing will cause the new effect to be enqueued.
Any previously enqueued item gets kicked out, thus maintaining the invariant that
at most one effect will be enqueued. (this is important so that things like RPCs
calls don't pile up)
CAUTION: This computation assumes that the input effect will always complete. If a
run of the effect raises, no more runs will ever get executed, since they will all
be waiting for the one that raised to complete. *)valpoll:here:[%call_pos]->('a->'bEffect.t)Value.t->('a->'bPoll_result.tEffect.t)Computation.tendmoduleDynamic_scope:sig(** 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]. *)type'at(** Creates a new variable for use with the rest of the functions. It is critically
important that the exact same [Dynamic_scope.t] is used in calls to [set] and the
corresponding [lookup]. *)valcreate:?sexp_of:('a->Sexp.t)->name:string->fallback:'a->unit->'at(** Creates a variable which is derived from another. Typically this is used to
project out a field of another dynamic variable which contains a record. *)valderived:?sexp_of:('a->Sexp.t)->'bt->get:('b->'a)->set:('b->'a->'b)->'at(** Given a ['a Dynamic_scope.t] and a ['a Value.t] evaluate a function whose
resulting Computation.t has access to the value via the [lookup] function. *)valset:here:[%call_pos]->'at->'aValue.t->inside:'rComputation.t->'rComputation.ttyperevert={revert:'a.'aComputation.t->'aComputation.t}(** like [set] but with the ability to revert the value in sub-computations. *)valset':here:[%call_pos]->'at->'aValue.t->f:(revert->'rComputation.t)->'rComputation.t(** Lookup attempts to find the value inside the nearest scope, but if there isn't
one, it falls back to default specified in [create]. *)vallookup:here:[%call_pos]->'at->'aComputation.tvalmodify:here:[%call_pos]->'at->change:('aValue.t->'aValue.t)->f:(revert->'rComputation.t)->'rComputation.tendmoduleIncr:sig(** A [Value.t] passed through [value_cutoff] will only trigger changes on its
dependents when the value changes according to the provided equality function *)valvalue_cutoff:here:[%call_pos]->'aValue.t->equal:('a->'a->bool)->'aComputation.t(** Use [compute] to move a function from the incremental world into the bonsai world. *)valcompute:here:[%call_pos]->'aValue.t->f:('aIncr.t->'bIncr.t)->'bComputation.t(** If you've got an incremental, you can convert it to a value with this function. *)valto_value:here:[%call_pos]->'aIncr.t->'aValue.t(** Compute some incremental value based on the time source. Using this time source
instead of [Incr.clock] is the more testable approach, since it allows tests to
control how time moves forward. *)valwith_clock:here:[%call_pos]->(Time_source.t->'aIncr.t)->'aComputation.tend(** This [Let_syntax] module is basically just {!Value.Let_syntax} with the addition of
the [sub] function, which operates on Computations.
By using the [let%sub] syntax extension, you can put a ['a Computation.t] on the RHS
and get a ['a Value.t] on the LHS.
{[
let%sub a = b in
...
]}
In the code above, [b] has type ['a Computation.t], and [a] has type ['a Value.t]. *)moduleLet_syntax:sig(*_ [let%pattern_bind] requires that a function named [return] with these semantics
exist here. *)valreturn:here:[%call_pos]->'aValue.t->'aComputation.tval(>>|):here:[%call_pos]->'aValue.t->('a->'b)->'bValue.tval(<*>):here:[%call_pos]->('a->'b)Value.t->'aValue.t->'bValue.tval(<$>):here:[%call_pos]->('a->'b)->'aValue.t->'bValue.tmoduleLet_syntax:sig(** [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:[%call_pos]] is used by the Bonsai debugger to
tie visualizations to precise source locations. *)valsub:here:[%call_pos]->'aComputation.t->f:('aValue.t->'bComputation.t)->'bComputation.tvalcutoff:here:[%call_pos]->'aValue.t->equal:('a->'a->bool)->'aValue.tvalswitch:here:Source_code_position.t->match_:intValue.t->branches:int->with_:(int->'aComputation.t)->'aComputation.tvalmap:here:[%call_pos]->'aValue.t->f:('a->'b)->'bValue.tvalmap2:here:[%call_pos]->'aValue.t->'bValue.t->f:('a->'b->'c)->'cValue.tvalreturn:here:[%call_pos]->'aValue.t->'aComputation.tvalboth:here:[%call_pos]->'aValue.t->'bValue.t->('a*'b)Value.tvalarr:here:[%call_pos]->'aValue.t->f:('a->'b)->'bComputation.tincludeMapnwithtype'at:='aValue.tvalarr2:here:[%call_pos]->'aValue.t->'bValue.t->f:('a->'b->'c)->'cComputation.tvalarr3:here:[%call_pos]->'aValue.t->'bValue.t->'cValue.t->f:('a->'b->'c->'d)->'dComputation.tvalarr4:here:[%call_pos]->'aValue.t->'bValue.t->'cValue.t->'dValue.t->f:('a->'b->'c->'d->'e)->'eComputation.tvalarr5:here:[%call_pos]->'aValue.t->'bValue.t->'cValue.t->'dValue.t->'eValue.t->f:('a->'b->'c->'d->'e->'f)->'fComputation.tvalarr6:here:[%call_pos]->'aValue.t->'bValue.t->'cValue.t->'dValue.t->'eValue.t->'fValue.t->f:('a->'b->'c->'d->'e->'f->'g)->'gComputation.tvalarr7:here:[%call_pos]->'aValue.t->'bValue.t->'cValue.t->'dValue.t->'eValue.t->'fValue.t->'gValue.t->f:('a->'b->'c->'d->'e->'f->'g->'h)->'hComputation.tendendmoduleTime_source=Time_sourcemoduleExpert:sig(** [thunk] will execute its argument exactly once per instantiation of the
computation. *)valthunk:here:[%call_pos]->(unit->'a)->'aComputation.t(** [assoc_on] is similar to [assoc], but allows the model to be keyed differently
than the input map. This comes with a few caveats:
- Inputs whose keys map to the same [model_key] will share the same model.
- The result of [get_model_key] is used in a bind, so it is expensive when it
changes.
[assoc] should almost always be used instead. Consider whether you really need the
additional power before reaching for this function. *)valassoc_on:here:[%call_pos]->('io_key,'io_cmp)Comparator.Module.t->('model_key,'model_cmp)Comparator.Module.t->('io_key,'data,'io_cmp)Map.tValue.t->get_model_key:('io_key->'data->'model_key)->f:('io_keyValue.t->'dataValue.t->'resultComputation.t)->('io_key,'result,'io_cmp)Map.tComputation.tendmoduleDebug:sig(** [on_change v ~f] executes the function [f] every time that [v] is recomputed. *)valon_change:here:[%call_pos]->'aValue.t->f:('a->unit)->unitComputation.t(** like [on_change], but specialized for printing a sexp of the value that you are
watching. *)valon_change_print_s:here:[%call_pos]->'aValue.t->('a->Sexp.t)->unitComputation.tvalto_dot:?pre_process:bool->'aComputation.t->stringvalbonsai_node_counts:?pre_process:bool->'aComputation.t->Bonsai.Private.Skeleton.Counts.tvalenable_incremental_annotations:unit->unitvaldisable_incremental_annotations:unit->unitendvalpath:here:[%call_pos]->unit->Path.tComputation.t(** 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. *)includeBonsai.Private.For_proc.Map_and_set0_outputwithmoduleValue:=ValueandmoduleComputation:=Computationend