12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006(** For expressing a computation that depends on variables and that can automatically
incrementally recompute after the values of some of the variables change.
{1 Incremental in a nutshell}
Incremental is used to define a collection of interdependent values, some of which are
"variables" set by user code and others that are defined via functions (in the
mathematical and programming senses) of other incremental values. Incremental
automatically tracks all the dependencies between incremental values and can, on
demand, propagate changed variables and recompute the incremental values that depend
on them.
To use incremental, one first creates a new instance via:
{[
module Inc : Incremental.S = Incremental.Make ()
]}
The functor application creates data structures that will be shared throughout the
lifetime of all incremental values used with this instance. Since [Incremental.Make]
is a generative functor, the type system enforces that different applications of the
functor deal with disjoint sets of incrementals.
For the remainder of this comment, we assume a particular [Inc] is [open]:
{[
open Inc
]}
As an example of a simple computation, suppose we have integer variables [x] and [y]
and want to keep an incremental value [z] defined by [z = x + y]. We could do this
with:
{[
let x = Var.create 13
let y = Var.create 17
let z = map2 (Var.watch x) (Var.watch y) ~f:(fun x y -> x + y)
]}
With this, as [x] and [y] change, incremental can recompute [z = x + y] on demand.
Incremental only recomputes values that are being "observed", which one indicates by
calling the [observe] function to get an "observer", e.g.:
{[
let z_o = observe z
]}
Incremental doesn't compute [z] every time [x] and [y] change. Rather, one must
explicitly tell incremental when one wants [z] (and all other observed values) to be
brought up to date, by calling [stabilize]:
{[
stabilize ();
]}
At this point, the value of [z] is [30], which we can verify by:
{[
assert (Observer.value_exn z_o = 30);
]}
If we change the value of [x] and then tell incremental to recompute observed values,
then the value of [z] will change appropriately:
{[
Var.set x 19;
stabilize ();
assert (Observer.value_exn z_o = 36);
]}
Another way to observe values is to use [Observer.on_update_exn], which attaches an
"on-update handler" to an observer -- the handler will be run after each stabilization
in which the observer's value changed (or was initialized) during stabilization.
User functions given to incremental should never raise any exceptions: doing so will
cause all future calls to [stabilize] to raise.
{1 The incremental DAG}
One can think of incrementals as forming a directed acyclic graph (DAG), where nodes
correspond to incremental values and there is an edge from node [n1] to node [n2] if
the value of [n2] depends on the value of [n1]. For example, the DAG for the above
example has an edge from [x] to [z] and from [y] to [z]. The graph must be acyclic in
order for the computation to be well defined. The graph is a DAG rather than a tree
because incremental values can be shared. Extending the above example, we might have:
{[
let w = map2 (Var.watch y) z ~f:(fun y z -> y - z)
]}
Both the node for [y] and the node for [z] are shared.
We will use "node" to mean "incremental value" when we want to emphasize some aspect
of the DAG.
Say that a node is "observed" if there is an observer for it (created via [observe]).
Say that a node is "necessary" if there is a path from that node to an observed node.
[stabilize] ensures that all necessary nodes have correct values; it will not compute
unnecessary nodes. An unobserved node becomes necessary by a call to [observe] or by
being used to compute an observed node; this will cause the appropriate DAG edges to
be added. A necessary node will become unnecessary if its observer (if any) becomes
unused and if the node is no longer used to compute any observed nodes. This will
cause the appropriate DAG edges to be removed.
Incremental does not know whether user-supplied functions (e.g. functions supplied to
[bind] or [map]) are side effecting, and will not evaluate them for side effect. If
the resulting incrementals are not necessary then the function will not be called.
{1 Stabilization}
[stabilize] traverses the DAG in topological order starting at variables that changed
since the last stabilization and recomputing their dependents. This is done by using
a "recompute heap" to visit the nodes in non-decreasing order of "height", which is a
over-approximation of the longest path from a variable to that node. To ensure that
each node is computed at most once and that its children are stabilized before it is
computed, nodes satisfy the property that if there is an edge from n1 to n2, then the
height of n1 is less than the height of n2.
[stabilize] repeats the following steps until the heap becomes empty:
1. remove from the recompute heap a node with the smallest height
2. recompute that node
3. if the node's value changes, then add its parents to the heap.
The definition of "changes" in step (3) is configurable by user code. By default, a
node is considered to change if its new value is not [phys_equal] to the previous
value. One can use [set_cutoff] on a node to change its cutoff function, e.g. for
[floats] one could cutoff propagation if the old value and new value are closer than
some threshold.
If [stabilize] ever raises due to an error, then the incremental system becomes
unusable, and all future calls to [stabilize] will immediately raise.
Stabilization uses a heap implemented with an array whose length is the max height, so
for good performance, the height of nodes must be small. There is an upper bound on
the height of nodes, [max_height_allowed], which defaults to 128. An attempt to
create a node with larger height will raise. One can dynamically increase
[max_height_allowed]; however, one should be wary of doing so, for performance
reasons.
{1 Bind}
Much of the power of incremental comes from [bind], also written [>>=]. As a
reminder, [bind] has this type:
{[
val bind : 'a t -> f:('a -> 'b t) -> 'b t
]}
[bind ta ~f] returns an incremental [tb] that behaves like [f a], where [a] is the
most recent value of [ta]. The implementation only calls [f] when the value of [ta]
changes. Thinking in terms of the DAG, [bind ta ~f] returns a node [tb] such that
whenever the value of [ta] changes, the implementation calls [f] to obtain a node
(possibly with an arbitrary DAG below it) that defines the value of [tb].
[bind] can be used to transition existing parts of the graph between necessary and
unnecessary. E.g.:
{[
val if_ : bool t -> a t -> a t -> a t
let if_ a b c = bind a ~f:(fun a -> if a then b else c)
]}
With [let t = if_ a b c], when [a] is [true], if [t] is necessary, then [b] will be
necessary, but [c] will not. And vice-versa when [a] is [false].
Even more, [bind] allows one to dynamically create an arbitrary graph based on the
value of some other incremental, and to "hide" that dynamism behind an ordinary
incremental value. One common way to use this is for dynamic reconfiguration, e.g.:
{[
let config_var = Var.create config in
bind (Var.watch config_var) ~f:(fun config -> ... )
]}
Then, whenever one wants to reconfigure the system, one does [Var.set config_var]
and then [stabilize], which will construct a new DAG according to the new config.
Bind nodes introduce special height constraints, so that stabilization is guaranteed
to recompute the left-hand side of a bind before recomputing any node created by the
right-hand side [f]. This avoids recomputing nodes created on the right-hand side
that would then become unnecessary when the left-hand side changes. More precisely,
in [t >>= f], any node created by [f] is made to have a height larger than [t]. This
rule applies also to bind nodes created by [f], so that ultimately the height of every
node is greater than the height of all the left-hand sides of the binds that were
involved in its creation. The height requirement does not apply to nodes returned by
[f] but not created by [f] -- such nodes depend on the bind in effect when they were
created, but have no dependence on [t].
When the left-hand side of a bind node changes, stabilization "invalidates" all the
nodes that depend on it (because they may use an old value of the left-hand side).
For example, consider:
{[
let t1 = map ... in
bind t2 ~f:(fun _ ->
let t3 = map ... in
map2 t1 t3 ~f:(...))
]}
In this example, [t1] is created outside of [bind t2], whereas [t3] is created by the
right-hand side of [bind t2]. So, [t3] depends on [t2] (and has a greater height),
whereas [t1] does not. And, in a stabilization in which [t2] changes, we are
guaranteed to not recompute the old [t3], but we have no such guarantee about [t1].
Furthermore, when [t2] changes, the old [t3] will be invalidated, whereas [t1] will
not.
Since [bind] essentially allows one to add arbitrary edges to the DAG, one can use it
to construct a cycle. [stabilize] will detect such cycles and raise.
{1 Garbage collection}
Incremental maintains three kinds of pointers:
- from nodes to their children (nodes they depend on).
- from necessary nodes to their necessary parents (nodes that depend on them).
- from observers to the nodes they observe.
So, all necessary nodes are kept alive, from the perspective of the garbage collector.
If an observer has no on-update handlers and user code no longer holds on to it,
incremental (via a finalizer on the observer), detects this and disallows future use
of the observer, making the node it observed unnecessary if it is not necessary for
another reason. One can eagerly remove an observer by calling [disallow_future_use].
Because finalizers may be called much later than when an observer actually becomes
unreachable, it is preferable to disable observers using [disallow_future_use] to
avoid useless propagation during stabilizations.
If an observer has on-update handlers, calling [disallow_future_use] is the only way
to have it removed.
{1 The implementation}
The key type in the implementation is [Node.t], which represents one node in the
incremental DAG. The node type is in fact the same as [Incremental.t], although this
type equivalence is not exposed. A node is a record with many fields (> 20). In
particular a node holds:
- kind -- the kind of node it is (const, var, map, bind, snapshot, etc.).
- value -- the node's current value.
- recompute id -- the stabilization number at which it was last recomputed.
- change id -- the stabilization number at which its value last changed.
- height -- the height of the node in the DAG.
- parents -- the necessary nodes that depend on it.
- children -- the nodes that it depends on.
- created_in -- the scope in which it was created.
Say that a node is "stale" if it has never been computed or if its recompute id is
less than the change id of one of its children. A node should be recomputed if it
is both necessary and stale.
The [State.t] type holds all the mutable data used to implement stabilization. In
particular, the incremental state contains:
- the current stabilization number
- the set of observers
- a recompute heap -- nodes that need to be recomputed, ordered by height.
- an adjust-heights heap -- nodes whose height needs increasing, ordered by height.
The goals of stabilization are to:
- compute all necessary nodes that need to be recomputed.
- only compute necessary nodes.
- compute each node at most once.
- only compute a node after ensuring its children are up to date.
To do this, incremental maintains the following invariants:
- [p] is in [c]'s parents iff ([c] is in [p]'s children && [p] is necessary)
- [p] is in the recompute heap iff [p] is necessary and stale.
- if [p] is a parent of [c], then [p]'s height is greater than [c]'s height.
The first invariant ensures that when a node's value changes, we can reach from it all
necessary nodes (and only the necessary nodes) that depend on it. The second
invariant ensures that that stabilization only computes necessary nodes. The third
invariant, combined with the fact that stabilization always recomputes a node from the
recompute-heap that has minimum height, ensures that we only compute a node after all
its children are stable, and that we compute each node at most once.
Finally, at the end of stabilization, the recompute heap is empty, so the invariant
implies that there are no necessary nodes that are stale, i.e. stabilization has
computed all necessary nodes that need to be recomputed.
{1 Maintaining the parent invariant}
Maintaining the invariant that a node has edges only to necessary parents requires
traversing a node's descendants when it transitions between necessary and unnecessary,
in order to add or remove parents as appropriate. For example, when an observer is
first added to an unnecessary node, the implementation visits all its descendants to
add parents. This is essentially a form of ref-counting, in which the counter is the
number of parents that a node has. There is no problem with cycles because the DAG
requirement on the graph is enforced.
{1 Maintaining the height invariant and checking for cycles}
Maintaining the invariant that a necessary node's height is larger than all of its
children requires adjusting heights when an edge is added to the DAG (e.g. when a bind
left-hand side changes). This is done using the "adjust-heights" heap. When an edge
is added, if the child's height is greater than or equal to the parent's height, then
the adjust-heights heap increases the height of the parent and all of the parent's
ancestors as necessary in order to restore the height invariant. This is done by
visiting ancestors in topological order, in increasing order of pre-adjusted height.
If during that traversal, the child of the original edge is visited, then there is a
cycle in the graph, and stabilization raises.
In pathological situations, the implementation will raise due to a cyclic graph even
though subsequent graph operations would eliminate the cycle. This is because the
cyclicity check happens after each edge is added, rather than waiting until a batch
of graph changes.
{1 Bind, scopes, and invalidation}
Much of the complexity of the implementation comes from [bind]. In [t >>= f], when
[f] is applied to the value of [t], all of the nodes that are created depend on that
value. If the value of [t] changes, then those nodes no longer make sense because
they depend on a stale value. It would be both wasteful and wrong to recompute any of
those "invalid" nodes. So, the implementation maintains the invariant that the height
of a necessary node is greater than the height of the left-hand side of the nearest
enclosing bind. That guarantees that stabilization will stabilize the left-hand side
before recomputing any nodes created on the right-hand side. Furthermore, if the
left-hand side's value changes, stabilization marks all the nodes on the right-hand
side as invalid. Such invalid nodes will typically be unnecessary, but there are
pathological cases where they remain necessary.
The bind height invariant is accomplished using a special "bind-lhs-change" node,
which is a parent of the bind-lhs and a child of the bind result. The incremental
state maintains the "current scope", which is the bind whose right-hand side is
currently being evaluated, or a special "top" scope if there is no bind in effect.
Each node has a [created_in] field set to the scope in effect when the node is
created. The implementation keeps for each scope, a singly-linked list of all nodes
created in that scope. Invalidation traverses this list, and recurs on bind nodes in
it to traverse their scopes as well.
[if_] and [join] are special cases of [bind] that manipulate the graph; however they
do not create new scopes. They use a similar lhs-change node to detect changes and
perform graph manipulation.
{1 Debugging}
For performance reasons, [Incremental] is built with debugging asserts disabled.
[Incremental_debug] is a library that uses the same code as [Incremental], but has
debugging asserts enabled (via an [IFDEF]). [Incremental_debug] is significantly
slower than [Incremental], but may detect a bug in the Incremental library that would
otherwise remain undetected by [Incremental].
{1 Reading guide}
Here's a breakdown of the modules in roughly dependency order.
{ul
{li [Import] -- imports from other libraries, and commonly used functions }
{li Basic types.
- [Cutoff] -- a cutoff function
- [On_update_handler] -- a function to run when a node's value changes
- [Node_id] -- an integer unique id for nodes
- [Raised_exn] -- a wrapper around [exn] that keeps a backtrace.
- [Sexp_of] -- interfaces for types that have [with sexp_of].
- [Stabilization_num] -- an abstract [int option], used to express the stabilization
cycle when something happens. }
{li [Types] -- mutually recursive types.
Many of the types used in the implementation are mutually recursive. They are
all defined in [Types]. Each type is then later defined in its own module, along
with [with fields, sexp]. }
{li [Kind] -- the variant with one constructor for each kind of node, plus a special
constructor for invalidated nodes. Many of the value-carrying variants also have a
module for its argument type:
- [Array_fold]
- [At]
- [At_intervals]
- [Bind]
- [Freeze]
- [If_then_else]
- [Join]
- [Snapshot]
- [Step_function_node]
- [Unordered_array_fold]
- [Var] }
{li [Scope] -- a packed bind. }
{li [Node] -- the main node type. }
{li [Internal_observer] }
{li [Observer] -- a [ref] wrapper around [Internal_observer], used so a finalizer
can detect when user code is done with an observer. }
{li [Recompute_heap] }
{li [Adjust_heights_heap] }
{li [Alarm_value] -- values stored in the timing wheel, for time-based nodes. }
{li [State] -- the record type will all data structures used for stabilization, and
the implementation of all the [Incremental] functions. }
{li [Incremental], the main functor, mostly a wrapper around [State]. }
{li [Incremental_unit_tests]. }
} *)openCoreopen!ImportmoduletypeMap_n_gen=sigtype('a,'w)tvalmap2:('a1,'w)t->('a2,'w)t->f:('a1->'a2->'b)->('b,'w)tvalmap3:('a1,'w)t->('a2,'w)t->('a3,'w)t->f:('a1->'a2->'a3->'b)->('b,'w)tvalmap4:('a1,'w)t->('a2,'w)t->('a3,'w)t->('a4,'w)t->f:('a1->'a2->'a3->'a4->'b)->('b,'w)tvalmap5:('a1,'w)t->('a2,'w)t->('a3,'w)t->('a4,'w)t->('a5,'w)t->f:('a1->'a2->'a3->'a4->'a5->'b)->('b,'w)tvalmap6:('a1,'w)t->('a2,'w)t->('a3,'w)t->('a4,'w)t->('a5,'w)t->('a6,'w)t->f:('a1->'a2->'a3->'a4->'a5->'a6->'b)->('b,'w)tvalmap7:('a1,'w)t->('a2,'w)t->('a3,'w)t->('a4,'w)t->('a5,'w)t->('a6,'w)t->('a7,'w)t->f:('a1->'a2->'a3->'a4->'a5->'a6->'a7->'b)->('b,'w)tvalmap8:('a1,'w)t->('a2,'w)t->('a3,'w)t->('a4,'w)t->('a5,'w)t->('a6,'w)t->('a7,'w)t->('a8,'w)t->f:('a1->'a2->'a3->'a4->'a5->'a6->'a7->'a8->'b)->('b,'w)tvalmap9:('a1,'w)t->('a2,'w)t->('a3,'w)t->('a4,'w)t->('a5,'w)t->('a6,'w)t->('a7,'w)t->('a8,'w)t->('a9,'w)t->f:('a1->'a2->'a3->'a4->'a5->'a6->'a7->'a8->'a9->'b)->('b,'w)tvalmap10:('a1,'w)t->('a2,'w)t->('a3,'w)t->('a4,'w)t->('a5,'w)t->('a6,'w)t->('a7,'w)t->('a8,'w)t->('a9,'w)t->('a10,'w)t->f:('a1->'a2->'a3->'a4->'a5->'a6->'a7->'a8->'a9->'a10->'b)->('b,'w)tvalmap11:('a1,'w)t->('a2,'w)t->('a3,'w)t->('a4,'w)t->('a5,'w)t->('a6,'w)t->('a7,'w)t->('a8,'w)t->('a9,'w)t->('a10,'w)t->('a11,'w)t->f:('a1->'a2->'a3->'a4->'a5->'a6->'a7->'a8->'a9->'a10->'a11->'b)->('b,'w)tvalmap12:('a1,'w)t->('a2,'w)t->('a3,'w)t->('a4,'w)t->('a5,'w)t->('a6,'w)t->('a7,'w)t->('a8,'w)t->('a9,'w)t->('a10,'w)t->('a11,'w)t->('a12,'w)t->f:('a1->'a2->'a3->'a4->'a5->'a6->'a7->'a8->'a9->'a10->'a11->'a12->'b)->('b,'w)tvalmap13:('a1,'w)t->('a2,'w)t->('a3,'w)t->('a4,'w)t->('a5,'w)t->('a6,'w)t->('a7,'w)t->('a8,'w)t->('a9,'w)t->('a10,'w)t->('a11,'w)t->('a12,'w)t->('a13,'w)t->f:('a1->'a2->'a3->'a4->'a5->'a6->'a7->'a8->'a9->'a10->'a11->'a12->'a13->'b)->('b,'w)tvalmap14:('a1,'w)t->('a2,'w)t->('a3,'w)t->('a4,'w)t->('a5,'w)t->('a6,'w)t->('a7,'w)t->('a8,'w)t->('a9,'w)t->('a10,'w)t->('a11,'w)t->('a12,'w)t->('a13,'w)t->('a14,'w)t->f:('a1->'a2->'a3->'a4->'a5->'a6->'a7->'a8->'a9->'a10->'a11->'a12->'a13->'a14->'b)->('b,'w)tvalmap15:('a1,'w)t->('a2,'w)t->('a3,'w)t->('a4,'w)t->('a5,'w)t->('a6,'w)t->('a7,'w)t->('a8,'w)t->('a9,'w)t->('a10,'w)t->('a11,'w)t->('a12,'w)t->('a13,'w)t->('a14,'w)t->('a15,'w)t->f:('a1->'a2->'a3->'a4->'a5->'a6->'a7->'a8->'a9->'a10->'a11->'a12->'a13->'a14->'a15->'b)->('b,'w)tendmoduletypeMap_n=sigtype'atincludeMap_n_genwithtype('a,'w)t:='atendmoduletypeBind_n_gen=sigtype('a,'w)tvalbind2:('a1,'w)t->('a2,'w)t->f:('a1->'a2->('b,'w)t)->('b,'w)tvalbind3:('a1,'w)t->('a2,'w)t->('a3,'w)t->f:('a1->'a2->'a3->('b,'w)t)->('b,'w)tvalbind4:('a1,'w)t->('a2,'w)t->('a3,'w)t->('a4,'w)t->f:('a1->'a2->'a3->'a4->('b,'w)t)->('b,'w)tendmoduletypeBind_n=sigtype'atincludeBind_n_genwithtype('a,'w)t:='atend(** [S_gen] is the type of the module returned by [Incremental.Make]. It is a
specialization of the interface of [Incremental], with:
- the ['w] state_witness type parameter removed
- the [State.t] argument removed
The comments for components of [S_gen] are in [module type Incremental] below. *)moduletypeS_gen=sigmoduleState:sigtypet[@@derivingsexp_of]includeInvariant.Swithtypet:=t(** [t] is the shared state for a call to [Incremental.Make]. *)valt:tvalkeep_node_creation_backtrace:t->boolvalset_keep_node_creation_backtrace:t->bool->unitvalmax_height_allowed:t->intvalset_max_height_allowed:t->int->unitvalnum_active_observers:t->intvalmax_height_seen:t->intvalnum_nodes_became_necessary:t->intvalnum_nodes_became_unnecessary:t->intvalnum_nodes_changed:t->intvalnum_nodes_created:t->intvalnum_nodes_invalidated:t->intvalnum_nodes_recomputed:t->intvalnum_nodes_recomputed_directly_because_one_child:t->intvalnum_nodes_recomputed_directly_because_min_height:t->intvalnum_stabilizes:t->intvalnum_var_sets:t->intmoduleStats:sigtypet[@@derivingsexp_of]endvalstats:t->Stats.tendtype'at[@@derivingsexp_of]type'aincremental:='atincludeInvariant.S1withtype'at:='atvalis_const:_t->boolvalis_valid:_t->boolvalis_necessary:_t->boolvalconst:'a->'atvalreturn:'a->'atvalmap:'at->f:('a->'b)->'btval(>>|):'at->('a->'b)->'btincludeMap_nwithtype'at:='atvalbind:'at->f:('a->'bt)->'btval(>>=):'at->('a->'bt)->'btincludeBind_nwithtype'at:='atmoduleInfix:sigval(>>|):'at->('a->'b)->'btval(>>=):'at->('a->'bt)->'btendvaljoin:'att->'atvalif_:boolt->then_:'at->else_:'at->'atvalfreeze:?when_:('a->bool)->'at->'atvaldepend_on:'at->depend_on:_t->'atvalnecessary_if_alive:'at->'atvalfor_all:booltarray->booltvalexists:booltarray->booltvalall:'atlist->'alisttvalboth:'at->'bt->('a*'b)tvalarray_fold:'atarray->init:'b->f:('b->'a->'b)->'btvalreduce_balanced:'atarray->f:('a->'b)->reduce:('b->'b->'b)->'btoptionmoduleUnordered_array_fold_update:sigtype('a,'b)t=|F_inverseof('b->'a->'b)|Updateof('b->old_value:'a->new_value:'a->'b)endvalunordered_array_fold:?full_compute_every_n_changes:int->'atarray->init:'b->f:('b->'a->'b)->update:('a,'b)Unordered_array_fold_update.t->'btvalopt_unordered_array_fold:?full_compute_every_n_changes:int->'aoptiontarray->init:'b->f:('b->'a->'b)->f_inverse:('b->'a->'b)->'boptiontvalsum:?full_compute_every_n_changes:int->'atarray->zero:'a->add:('a->'a->'a)->sub:('a->'a->'a)->'atvalopt_sum:?full_compute_every_n_changes:int->'aoptiontarray->zero:'a->add:('a->'a->'a)->sub:('a->'a->'a)->'aoptiontvalsum_int:inttarray->inttvalsum_float:floattarray->floattmoduleScope:sigtypetvaltop:tvalcurrent:unit->tvalwithin:t->f:(unit->'a)->'aendmoduleVar:sigtype'at[@@derivingsexp_of]valcreate:?use_current_scope:bool->'a->'atvalset:'at->'a->unitvalwatch:'at->'aincrementalvalvalue:'at->'avallatest_value:'at->'avalreplace:'at->f:('a->'a)->unitendmoduleObserver:sigtype'at[@@derivingsexp_of]includeInvariant.S1withtype'at:='atvalobserving:'at->'aincrementalvaluse_is_allowed:_t->boolvalvalue:'at->'aOr_error.tvalvalue_exn:'at->'amoduleUpdate:sigtype'at=|Initializedof'a|Changedof'a*'a|Invalidated[@@derivingcompare,sexp_of]endvalon_update_exn:'at->f:('aUpdate.t->unit)->unitvaldisallow_future_use:_t->unitendvalobserve:?should_finalize:bool->'at->'aObserver.tmoduleUpdate:sigtype'at=|Necessaryof'a|Changedof'a*'a|Invalidated|Unnecessary[@@derivingcompare,sexp_of]endvalon_update:'at->f:('aUpdate.t->unit)->unitvalstabilize:unit->unitvalam_stabilizing:unit->boolmoduleCutoff:sigtype'at[@@derivingsexp_of]includeInvariant.S1withtype'at:='atvalcreate:(old_value:'a->new_value:'a->bool)->'atvalof_compare:('a->'a->int)->'atvalof_equal:('a->'a->bool)->'atvalalways:_tvalnever:_tvalphys_equal:_tvalpoly_equal:_tvalshould_cutoff:'at->old_value:'a->new_value:'a->boolvalequal:'at->'at->boolendvalset_cutoff:'at->'aCutoff.t->unitvalget_cutoff:'at->'aCutoff.tvallazy_from_fun:(unit->'a)->'aLazy.tvaldefault_hash_table_initial_size:intvalmemoize_fun:?initial_size:int->'aBase.Hashtbl.Key.t->('a->'b)->('a->'b)Staged.tvalmemoize_fun_by_key:?initial_size:int->'keyBase.Hashtbl.Key.t->('a->'key)->('a->'b)->('a->'b)Staged.tvalweak_memoize_fun:?initial_size:int->'aBase.Hashtbl.Key.t->('a->'bHeap_block.t)->('a->'bHeap_block.t)Staged.tvalweak_memoize_fun_by_key:?initial_size:int->'keyBase.Hashtbl.Key.t->('a->'key)->('a->'bHeap_block.t)->('a->'bHeap_block.t)Staged.tvaluser_info:_t->Info.toptionvalset_user_info:_t->Info.toption->unitvalappend_user_info_graphviz:_t->label:stringlist->attrs:stringString.Map.t->unitmoduleNode_value:sigtype'at=|Invalid|Necessary_maybe_staleof'aoption|Unnecessary_maybe_staleof'aoption[@@derivingsexp_of]end(** [node_value t] returns whatever value [t] happens to have in it, regardless of
whether [t] is valid, necessary, or stale. One should use [observe] for a more
sensible semantics, reserving [node_value] for debugging. *)valnode_value:'at->'aNode_value.tmodulePacked:sigtypetvalsave_dot:Out_channel.t->tlist->unitvalsave_dot_to_file:string->tlist->unitvalappend_user_info_graphviz:t->label:stringlist->attrs:stringString.Map.t->unitendvalpack:_t->Packed.tvalsave_dot:Out_channel.t->unitvalsave_dot_to_file:string->unitmoduleLet_syntax:sigvalreturn:'a->'atval(>>|):'at->('a->'b)->'btval(>>=):'at->('a->'bt)->'btmoduleLet_syntax:sigvalbind:'at->f:('a->'bt)->'btvalreturn:'a->'atincludeBind_nwithtype'at:='atvalmap:'at->f:('a->'b)->'btincludeMap_nwithtype'at:='atvalboth:'at->'bt->('a*'b)tmoduleOpen_on_rhs:sigvalwatch:'aVar.t->'atendendendmoduleBefore_or_after:sigtypet=|Before|After[@@derivingsexp_of]endmoduleStep_function=Step_functionmoduleClock:sigtypet[@@derivingsexp_of]valdefault_timing_wheel_config:Timing_wheel.Config.tvalcreate:?timing_wheel_config:Timing_wheel.Config.t->start:Time_ns.t->unit->tvalalarm_precision:t->Time_ns.Span.tvaltiming_wheel_length:t->intvalnow:t->Time_ns.tvalwatch_now:t->Time_ns.tincrementalvaladvance_clock:t->to_:Time_ns.t->unitvaladvance_clock_by:t->Time_ns.Span.t->unitvalat:t->Time_ns.t->Before_or_after.tincrementalvalafter:t->Time_ns.Span.t->Before_or_after.tincrementalvalat_intervals:t->Time_ns.Span.t->unitincrementalvalstep_function:t->init:'a->(Time_ns.t*'a)list->'aincrementalvalincremental_step_function:t->'aStep_function.tincremental->'aincrementalvalsnapshot:t->'aincremental->at:Time_ns.t->before:'a->'aincrementalOr_error.tendmoduleExpert:sigmoduleDependency:sigtype'at[@@derivingsexp_of]valcreate:?on_change:('a->unit)->'aincremental->'atvalvalue:'at->'aendmoduleNode:sigtype'at[@@derivingsexp_of]valcreate:?on_observability_change:(is_now_observable:bool->unit)->(unit->'a)->'atvalwatch:'at->'aincrementalvalmake_stale:_t->unitvalinvalidate:_t->unitvaladd_dependency:_t->_Dependency.t->unitvalremove_dependency:_t->_Dependency.t->unitendmoduleStep_result:sigtypet=|Keep_going|Done[@@derivingsexp_of]endvaldo_one_step_of_stabilize:unit->Step_result.tendendmoduletypeIncremental=sig(** A [State.t] holds shared state used by all the incremental functions. *)moduleState:sigtype'wt[@@derivingsexp_of]moduletypeS=sigtypestate_witness[@@derivingsexp_of]valt:state_witnesstendvalcreate:?max_height_allowed:int(** default is 128 *)->unit->(moduleS)(** If [keep_node_creation_backtrace], then whenever a new node is created,
incremental will call [Backtrace.get] and store the result in the node. The
backtrace will then appear in subsequent error messages when the node is pretty
printed. *)valkeep_node_creation_backtrace:_t->boolvalset_keep_node_creation_backtrace:_t->bool->unitvalmax_height_allowed:_t->int(** [set_max_height_allowed t height] sets the maximum allowed height of nodes.
[set_max_height_allowed] raises if called during stabilization, or if [height <
max_height_seen t]. *)valset_max_height_allowed:_t->int->unit(** [num_active_observers] returns (in constant time) the number of observers that
have been created and not yet disallowed (either explicitly or via
finalization). *)valnum_active_observers:_t->int(** {2 constant-time stats} These are counters that are constant time to read, and
that are automatically updated in the ordinary course. *)valmax_height_seen:_t->intvalnum_nodes_became_necessary:_t->intvalnum_nodes_became_unnecessary:_t->int(** Number of times a node has seen its value changed, the determination of which
depends on the choice of cutoff. *)valnum_nodes_changed:_t->intvalnum_nodes_created:_t->intvalnum_nodes_invalidated:_t->intvalnum_nodes_recomputed:_t->intvalnum_nodes_recomputed_directly_because_one_child:_t->intvalnum_nodes_recomputed_directly_because_min_height:_t->intvalnum_stabilizes:_t->intvalnum_var_sets:_t->int(** [Stats] contains information about the DAG intended for human consumption.
[stats] takes time proportional to the number of necessary nodes. *)moduleStats:sigtypet[@@derivingsexp_of]endvalstats:_t->Stats.tend(** [type ('a,'w) t] is the type of incrementals that have a value of type ['a], with a
state witness of type ['w].
Incrementals are not covariant, i.e. we do not have [(+'a, _) t] -- consider,
e.g. [set_cutoff] and [get_cutoff]. However, if you have types [a1] and [a2] where
[a1] is a subtype of [a2], and a value [t1 : a1 t], then the following builds an
incremental value of type [a2 t]:
{[
let t2 : a2 t = t1 >>| fun a1 -> (a1 : a1 :> a2)
]} *)type('a,'w)t[@@derivingsexp_of]type('a,'w)incremental:=('a,'w)tincludeInvariant.S2withtype('a,'w)t:=('a,'w)tvalstate:(_,'w)t->'wState.t(** If [is_const t] then [t] is a constant-valued incremental. [is_const (const a)] is
true. *)valis_const:_t->boolvalis_valid:_t->boolvalis_necessary:_t->bool(** {1 Creating incrementals} *)(** [const state a] returns an incremental whose value never changes. It is the same as
[return], but reads more clearly in many situations because it serves as a nice
reminder that the incremental won't change (except possibly be invalidated). *)valconst:'wState.t->'a->('a,'w)tvalreturn:'wState.t->'a->('a,'w)t(** [map t1 ~f] returns an incremental [t] that maintains its value as [f a], where [a]
is the value of [t1]. [map2], [map3], ..., [map9] are the generalizations to more
arguments. If you need map<N> for some N > 9, it can easily be added, but also see
[array_fold] and [unordered_array_fold].
[f] should not create incremental nodes but this behavior is not checked; if you
want to create incremental nodes, use [bind]. The invalidation machinery that is
used with [bind] is not used with [map]. *)valmap:('a,'w)t->f:('a->'b)->('b,'w)tval(>>|):('a,'w)t->('a->'b)->('b,'w)tincludeMap_n_genwithtype('a,'w)t:=('a,'w)t(** [bind t1 ~f] returns an incremental [t2] that behaves like [f v], where [v] is the
value of [t1]. If [t1]'s value changes, then incremental applies [f] to that new
value and [t2] behaves like the resulting incremental.
[bind] can be significantly more expensive than [map] during stabilization, because,
when its left-hand side changes, it requires modification of the incremental DAG,
while [map] simply flows values along the DAG. Thus it is preferable to use [map]
(and its n-ary variants above) instead of [bind] unless one actually needs [bind]'s
power.
[bind2 t1 t2 ~f] is:
{[
bind (map2 t1 t2 ~f:(fun v1 v2 -> (v1, v2)))
~f:(fun (v1, v2) -> f v1 v2)
]}
This is equivalent to [bind t1 ~f:(fun v1 -> bind t2 ~f:(fun v2 -> f v1 v2))] but
more efficient due to using one bind node rather than two. The other [bind<N>]
functions are generalize to more arguments. *)valbind:('a,'w)t->f:('a->('b,'w)t)->('b,'w)tval(>>=):('a,'w)t->('a->('b,'w)t)->('b,'w)tincludeBind_n_genwithtype('a,'w)t:=('a,'w)tmoduleInfix:sigval(>>|):('a,'w)t->('a->'b)->('b,'w)tval(>>=):('a,'w)t->('a->('b,'w)t)->('b,'w)tend(** [join t] returns an incremental that behaves like the incremental that [t] currently
holds. *)valjoin:(('a,'w)t,'w)t->('a,'w)t(** [if_ tb ~then_ ~else_] returns an incremental [t] that holds the value of [then_] if
[tb] is true, the value of [else_] if [tb] is false. Note that [t] only depends on
one of [then_] or [else_] at a time, i.e. [if_ tb ~then_ ~else] is like:
{[
bind b ~f:(fun b -> if b then then_ else else_)
]}
which is not the same as:
{[
map3 b then_ else_ ~f:(fun b then_ else_ -> if b then then_ else else_)
]} *)valif_:(bool,'w)t->then_:('a,'w)t->else_:('a,'w)t->('a,'w)t(** [freeze ?when_ t] returns an incremental whose value is [t]'s value [v] until the
first stabilization in which [when_ v] holds, at which point the freeze node's value
becomes constant and never changes again. Calling [freeze t] forces [t] to be
necessary until it freezes regardless of whether the freeze node is necessary, but
not thereafter (although of course [t] could remain necessary for other reasons).
The result of [freeze t], once frozen, will never be invalidated, even if [t] is
invalidated, and even if the scope in which the freeze is created is invalidated.
However, prior to [when_ v] becoming true, [freeze t] can be invalidated. *)valfreeze:?when_:('a->bool)->('a,'w)t->('a,'w)t(** [depend_on input ~depend_on] returns an [output] whose value is the same as
[input]'s value, such that [depend_on] is necessary so long as [output] is
necessary. It is like:
{[
map2 input depend_on ~f:(fun a _ -> a)
]}
but with a cutoff such that [output]'s value only changes when [input]'s value
changes. *)valdepend_on:('a,'w)t->depend_on:(_,'w)t->('a,'w)t(** [necessary_if_alive input] returns [output] that has the same value and cutoff as
[input], such that as long as [output] is alive, [input] is necessary. *)valnecessary_if_alive:('a,'w)t->('a,'w)t(** [for_all ts] returns an incremental that is [true] iff all [ts] are [true]. *)valfor_all:'wState.t->(bool,'w)tarray->(bool,'w)t(** [exists ts] returns an incremental that is [true] iff at least one of the [ts] is
[true]. *)valexists:'wState.t->(bool,'w)tarray->(bool,'w)t(** [all ts] returns an incremental whose value is a list of the values of all of the
[ts]. In any stabilization where any of the [ts] changes, the entire list is
recreated (once all of the [ts] have stabilized). This essentially an [array_fold]
over the [ts]. *)valall:'wState.t->('a,'w)tlist->('alist,'w)t(** [both t1 t2] returns an incremental whose value is pair of values of [t1] and [t2].
Both [map (both t1 t2) ~f] and [map2 t1 t2 ~f:(fun a1 a2 -> f (a1, a2))] return an
incremental with the same behavior, but the [map2] version is more efficient,
because it creates a single node, whereas the [both] version creates two nodes. *)valboth:('a,'w)t->('b,'w)t->('a*'b,'w)t(** {1 Array folds and sums} *)(** [array_fold ts ~init ~f] creates an incremental [t] whose value is:
{[
Array.fold ts ~init ~f:(fun ac t -> f ac (value t))
]}
In a stabilization during which any of the [ts] changes, the entire fold will be
computed once all of the [ts] have been computed. *)valarray_fold:'wState.t->('a,'w)tarray->init:'b->f:('b->'a->'b)->('b,'w)t(** [reduce_balanced ts ~f ~reduce] does a fold-like operation over [ts]. Unlike
[array_fold], the operation will be computed in [O(min(n, k * log(n)))] time, where
[n] is the size of [ts] and [k] is the number of elements of [ts] that have changed
since the last stabilization.
Generally, if most or all of [ts] are changing between stabilizations, using
[array_fold] will have better constant factors.
The [reduce] argument must be an associative operation:
[reduce a (reduce b c) = (reduce (reduce a b) c)].
[None] is returned upon supplying an empty array. *)valreduce_balanced:'wState.t->('a,'w)tarray->f:('a->'b)->reduce:('b->'b->'b)->('b,'w)toptionmoduleUnordered_array_fold_update:sigtype('a,'b)t=|F_inverseof('b->'a->'b)|Updateof('b->old_value:'a->new_value:'a->'b)end(** [unordered_array_fold ts ~init ~f ~update] folds over the [ts]. Unlike
[array_fold], the fold will be computed in time proportional to the number of [ts]
that change rather than the number of [ts]. In a stabilization, for each [t] in
[ts] that changes from [old_value] to [new_value], the value of the unordered-array
fold, [b], will change depending on [update]:
- [F_inverse f_inverse]: from [b] to [f (f_inverse b old_value) new_value]
- [Update update]: from [b] to [update b ~old_value ~new_value]
The [t]'s that change may take effect in any order.
If repeated changes might accumulate error, one can cause the fold to be fully
computed after every [full_compute_every_n_changes] changes. If you do not supply
[full_compute_every_n_changes], then full computes will never happen after the
initial one. *)valunordered_array_fold:'wState.t->?full_compute_every_n_changes:int->('a,'w)tarray->init:'b->f:('b->'a->'b)->update:('a,'b)Unordered_array_fold_update.t->('b,'w)t(** [opt_unordered_array_fold] is like [unordered_array_fold], except that its result is
[Some] iff all its inputs are [Some]. *)valopt_unordered_array_fold:'wState.t->?full_compute_every_n_changes:int->('aoption,'w)tarray->init:'b->f:('b->'a->'b)->f_inverse:('b->'a->'b)->('boption,'w)t(** [sum ts ~zero ~add ~sub ?full_compute_every_n_changes] returns an incremental that
maintains the sum of the [ts]. It uses [unordered_array_fold] so that the work
required to maintain the sum is proportional to the number of [ts] that change
(i.e. one [sub] and one [add] per change).
[opt_sum] is like [sum], except that its result is [Some] iff all its inputs are
[Some]. *)valsum:'wState.t->?full_compute_every_n_changes:int->('a,'w)tarray->zero:'a->add:('a->'a->'a)->sub:('a->'a->'a)->('a,'w)tvalopt_sum:'wState.t->?full_compute_every_n_changes:int->('aoption,'w)tarray->zero:'a->add:('a->'a->'a)->sub:('a->'a->'a)->('aoption,'w)t(** [sum_int ts] = [sum ts ~zero:0 ~add:(+) ~sub:(-)] *)valsum_int:'wState.t->(int,'w)tarray->(int,'w)t(** [sum_float ts] is:
{[
sum ts ~zero:0.0 ~add:(+.) ~sub:(-.)
~full_compute_every_n_changes:(Array.length ts)
]}
This uses [sum] for fast update, with a full recompute every [length ts] changes to
cut down on floating-point error. *)valsum_float:'wState.t->(float,'w)tarray->(float,'w)t(** The stack of bind left-hand sides currently in effect is the current "scope". In
order to create a function in one scope and apply it in a different scope, one must
manually save and restore the scope. Essentially, the scope should be part of every
closure that constructs incrementals. For example:
{[
bind t1 ~f:(fun i1 ->
let f t2 = map t2 ~f:(fun i2 -> i1 + i2) in
bind t3 ~f:(fun i -> ... f ...);
bind t4 ~f:(fun i -> ... f ...));
]}
In the above code, the calls to [f] will create a map node that unnecessarily
depends on the left-hand side of the most recent bind ([t3] or [t4]). To eliminate
the unnecessary dependence, one should save and restore the scope for [f]:
{[
bind t1 ~f:(fun i1 ->
let scope = Scope.current state in
let f t2 =
Scope.within state scope ~f:(fun () -> map t2 ~f:(fun i2 -> i1 + i2)) in
bind t3 ~f:(fun i -> ... f ...);
bind t4 ~f:(fun i -> ... f ...))
]}
[lazy_from_fun] and [memoize_fun] capture some common situations in which one would
otherwise need to use [Scope.within]. *)moduleScope:sigtype'wt(** [top] is the toplevel scope. *)valtop:_t(** [current ()] returns the scope in which a node would be created right now. *)valcurrent:'wState.t->unit->'wt(** [within t f] runs [f] in scope [t], which causes all nodes created by [f] to be in
scope [t]. An exception raised by [f] will be raised by [within] in the usual
way. *)valwithin:'wState.t->'wt->f:(unit->'a)->'aendmoduleVar:sigtype('a,'w)t[@@derivingsexp_of](** By default, a variable is created in [Scope.top], on the theory that its value
depends on external stimuli (via [Var.set]), not on the current scope. However,
in some situations it is useful to supply [~use_current_scope:true] to create a
variable that is invalidated when the current scope is invalidated, e.g. if one
wants to use [on_update (watch var) ~f:(function Invalidated -> ... | ...)] to
remove the external stimulus that was setting [var].
It is allowed to do [let t = create a] during stabilization; for that
stabilization, [watch t] will have value [a]. *)valcreate:'wState.t->?use_current_scope:bool(** default is [false] *)->'a->('a,'w)t(** [set t a] sets the value of [t] to [a]. Outside of stabilization, subsequent
calls to [Var.value t] will see [a], but the [set] will not have any effect on
incrementals until the next stabilization, at which point [watch t] will take on
whatever [value t] was at the start of stabilization, causing incremental
recomputation as usual.
During a stabilization, calling [set] will behave as if [set] was called after
stabilization finished: the new value will not be seen (by [value v] or [watch v])
until after the stabilization finishes. *)valset:('a,_)t->'a->unit(** [watch t] returns an incremental that tracks the value of [t]. For a given [t],
all calls to [watch t] return the same incremental. *)valwatch:('a,'w)t->('a,'w)incremental(** [value t] returns the value most recently [set] for [t] outside of
stabilization. *)valvalue:('a,_)t->'a(** [latest_value t] returns the value most recently [set] for [t]. It can differ
from [value t] only during stabilization. *)vallatest_value:('a,_)t->'a(** [replace t ~f] = [set t (f (latest_value t))] *)valreplace:('a,_)t->f:('a->'a)->unitendmoduleObserver:sigtype('a,'w)t[@@derivingsexp_of]includeInvariant.S2withtype('a,'b)t:=('a,'b)tvalobserving:('a,'w)t->('a,'w)incrementalvaluse_is_allowed:_t->bool(** [value t] returns the current value of [t], or [Error] if [t] does not currently
have a stable value. In particular, [value t] will return [Error] in the
following situations:
- in the middle of stabilization.
- if [stabilize] has not been called since [t] was created.
- if [disallow_future_use t] has been called.
- if [observing t] is invalid.
Rather than using [value] in a function that runs during stabilization, one should
use [map] or [bind] to express the dependence of an incremental computation on an
incremental. *)valvalue:('a,_)t->'aOr_error.tvalvalue_exn:('a,_)t->'amoduleUpdate:sigtype'at=|Initializedof'a|Changedof'a*'a(** [Changed (old_value, new_value)] *)|Invalidated[@@derivingcompare,sexp_of]end(** [on_update_exn t ~f] calls [f] after the current stabilization and after each
subsequent stabilization in which [t] changes, until [disallow_future_use t] is
called. [f] will be called at most once per stabilization. Here is a state
diagram for the allowable sequences of [Update.t]'s that can be supplied to a
particular [f]:
{v
/-----------------------------------------------------\
| / |
| | v
Start ------> Initialized ------> Changed ------> Invalidated
^ |
\--/
v}
[on_update_exn] raises if [disallow_future_use t] was previously called. *)valon_update_exn:('a,_)t->f:('aUpdate.t->unit)->unit(** After [disallow_future_use t]:
- [on_update_exn t] and [value_exn t] will raise, and [value t] will return
[Error].
- [t]'s on-update handlers will never run again.
- At the next call to [stabilize], before recomputing nodes, incremental will mark
[t] as unobserved, and mark as unnecessary all nodes that were necessary only to
maintain [t]. *)valdisallow_future_use:_t->unitend(** [observe t] returns a new observer for [t]. [observe] raises if called during
stabilization.
By default, an observer has a finalizer that calls [disallow_future_use] when the
observer is no longer referenced. One can use [~should_finalize:false] to cause the
finalizer to not be created, in which case the observer will live until
[disallow_future_use] is explicitly called. *)valobserve:?should_finalize:bool(** default is [true] *)->('a,'w)t->('a,'w)Observer.tmoduleUpdate:sigtype'at=|Necessaryof'a|Changedof'a*'a(** [Changed (old_value, new_value)] *)|Invalidated|Unnecessary[@@derivingcompare,sexp_of]end(** [on_update t ~f] is similar to [Observer.on_update_exn], but it does not cause [t]
to be necessary. Instead of the [Initialized] update, there are updates for when a
node becomes [Necessary] or [Unnecessary]. Here is a state diagram for the
allowable sequences of [Update.t]'s that can be supplied to a particular [f]:
{v
/-----------------------------------------------------\
| / |
| | v
Start ------> Necessary ----------> Changed ------> Invalidated
| | ^ | ^ | ^
| | | /---------/ \--/ |
| v | v |
\----------> Unnecessary -----------------------------/
v}
If [t] gets a new value during a stabilization but is unnecessary at the end of it,
[f] will _not_ be called with [Changed], but with [Unnecessary] if allowed by the
transition diagram. I.e. if the prior call to [f] was with [Necessary] or
[Changed], [f] will be called with [Unnecessary]. If the prior call to [f] was with
[Invalidated] or [Unnecessary], then [f] will not be called.
One should typically use [Observer.on_update_exn], unless the [Unnecessary] updates
are needed. *)valon_update:('a,_)t->f:('aUpdate.t->unit)->unit(** {1 Stabilization} *)(** [stabilize ()] recomputes all incrementals that are necessary and stale. I.e. it
propagates changes from variables that have been set to the necessary incrementals
that depend on them, stopping propagation as per cutoffs. *)valstabilize:_State.t->unitvalam_stabilizing:_State.t->bool(** {1 Cutoffs} *)(** An ['a Cutoff.t] is a function that returns [true] if propagation of changes should
be cutoff at a node based on the old value and the (possible) new value of the
node. *)moduleCutoff:sigtype'at[@@derivingsexp_of]includeInvariant.S1withtype'at:='atvalcreate:(old_value:'a->new_value:'a->bool)->'atvalof_compare:('a->'a->int)->'atvalof_equal:('a->'a->bool)->'atvalalways:_tvalnever:_tvalphys_equal:_tvalpoly_equal:_tvalshould_cutoff:'at->old_value:'a->new_value:'a->bool(** One can use [equal] in combination with [get_cutoff] to check if a node has a
particular cutoff function. [equal] uses [Core.phys_equal] for functional
values supplied to [create] and [of_compare]. *)valequal:'at->'at->boolend(** [set_cutoff t cutoff] replaces the current cutoff function for [t] with [cutoff].
[cutoff] will be called any time [t] is recomputed, with [old_value] being the value
of [t] before the recomputation and [new_value] being the value that just
recomputed. If [cutoff ~old_value ~new_value], then [t]'s value will remain as
[old_value] ([new_value] is discarded) and anything depending on [t] will not be
recomputed (at least not because of [t]). If [not (cutoff ~old_value ~new_value)],
then [t]'s value will become [new_value], and all nodes depending on [t] will
recomputed.
A reasonable choice for [cutoff] is an equality function on ['a].
The default cutoff for every node is [phys_equal]. For example, this means that a
[unit incremental] would only fire once; to disable this, use [set_cutoff t
Cutoff.never]. *)valset_cutoff:('a,_)t->'aCutoff.t->unitvalget_cutoff:('a,_)t->'aCutoff.t(** [lazy_from_fun f] is like [Lazy.from_fun f], except that the nodes created by [f]
will be created in the scope in which [lazy_from_fun] was called, rather than in the
scope of the piece of code that first forces the resulting lazy. Not using this
function when defining lazy values is likely to result in exceptions being thrown by
incremental. As a rule of thumb, all [lazy e] that might create incremental nodes
should be replaced by [lazy_from_fun (fun () -> e)].
As usual with [Lazy], if [f] raises, then that exception will be raised when calling
[Lazy.force]. *)vallazy_from_fun:_State.t->(unit->'a)->'aLazy.tvaldefault_hash_table_initial_size:int(** [memoize_fun f hashable] returns a function [m] that is a memoized version of [f]
that will run [f a] on each distinct [a] that [m] is applied to, memoize the result
(in a hash table), and thereafter for [a], [m] will return the memoized result.
When [m] is called, it uses [Scope.within] to run [f] in the scope that was in
effect when [memoize_fun f] was called. This is essential to correctly capture the
dependence of nodes that [f] creates on values that [f] is closed over, which may in
turn depend on the left-hand sides of binds in the scope in effect when [memoize_fun
f] was called. Furthermore, nodes that [f] creates do not depend on the scope in
effect when [m] is called.
[memoize_fun_by_key] is a generalization that allows one to memoize over values that
contain a uniquely identifying key, but also have other data. *)valmemoize_fun:?initial_size:int(** default is [4]. *)->_State.t->'aBase.Hashtbl.Key.t->('a->'b)->('a->'b)Staged.tvalmemoize_fun_by_key:?initial_size:int(** default is [4]. *)->_State.t->'keyBase.Hashtbl.Key.t->('a->'key)->('a->'b)->('a->'b)Staged.t(** The weak versions of the memoization functions use a {!Weak_hashtbl} for the memo
table. This keeps a weak pointer to each result, and so the garbage collector
automatically removes unused results. Furthermore, [stabilize] removes the table
entries whose result is unused. *)valweak_memoize_fun:?initial_size:int(** default is [4]. *)->_State.t->'aBase.Hashtbl.Key.t->('a->'bHeap_block.t)->('a->'bHeap_block.t)Staged.tvalweak_memoize_fun_by_key:?initial_size:int(** default is [4]. *)->_State.t->'keyBase.Hashtbl.Key.t->('a->'key)->('a->'bHeap_block.t)->('a->'bHeap_block.t)Staged.t(** For debugging purposes, one can store an arbitrary [Info.t] in a node. This will
be displayed as part of a node in error messages. *)valuser_info:_t->Info.toptionvalset_user_info:_t->Info.toption->unit(** [append_user_info_graphviz] also modifies the [user_info] field on the node, but in
a way that allows the caller to set the label and the properties on the node when a
graphviz graph is produced by calling [save_dot] or [save_dot_to_file]. *)valappend_user_info_graphviz:_t->label:stringlist->attrs:stringString.Map.t->unitmoduleNode_value:sigtype'at=|Invalid|Necessary_maybe_staleof'aoption|Unnecessary_maybe_staleof'aoption[@@derivingsexp_of]endvalnode_value:('a,_)t->'aNode_value.tmodulePacked:sigtypet(** [save_dot out_channel ts] outputs to [out_channel] the DAG of nodes in [ts] and
all their descendants, in dot format. *)valsave_dot:Out_channel.t->tlist->unitvalsave_dot_to_file:string->tlist->unitendvalpack:_t->Packed.t(** [save_dot file] outputs to [file] the DAG of all necessary nodes, in dot format. *)valsave_dot:_State.t->Out_channel.t->unitvalsave_dot_to_file:_State.t->string->unit(** This [Let_syntax] allows you to write expressions like
{[
let open Incr.Let_syntax in
let%map_open some_incr = watch some_variable
and another_incr = ...
and ...
in
...expression involving some_incr, another_incr, etc...
]}
Note that this is less efficient than using [map3], [map4], etc., as the latter
produces fewer intermediate nodes. You can also use [let%mapn] syntax to use n-ary
map functions efficiently. *)moduleLet_syntax:sigval(>>|):('a,'w)t->('a->'b)->('b,'w)tval(>>=):('a,'w)t->('a->('b,'w)t)->('b,'w)tmoduleLet_syntax:sigvalbind:('a,'w)t->f:('a->('b,'w)t)->('b,'w)tincludeBind_n_genwithtype('a,'w)t:=('a,'w)tvalmap:('a,'w)t->f:('a->'b)->('b,'w)tincludeMap_n_genwithtype('a,'w)t:=('a,'w)tvalboth:('a,'w)t->('b,'w)t->('a*'b,'w)tmoduleOpen_on_rhs:sig(** This is [Var.watch]. *)valwatch:('a,'w)Var.t->('a,'w)tendendendmoduleBefore_or_after:sigtypet=|Before|After[@@derivingsexp_of]endmoduleStep_function=Step_function(** Incremental has a timing-wheel-based clock, and lets one build incremental values
that change as its time passes. One must explicitly call [advance_clock] to change
incremental's clock; there is no implicit call based on the passage of time. *)moduleClock:sigtype'wt[@@derivingsexp_of](** The default timing-wheel configuration, with one millisecond precision, and alarms
allowed arbitrarily far in the future. *)valdefault_timing_wheel_config:Timing_wheel.Config.tvalcreate:'wState.t->?timing_wheel_config:Timing_wheel.Config.t->start:Time_ns.t->unit->'wt(** The [alarm_precision] of the underlying timing wheel. *)valalarm_precision:_t->Time_ns.Span.tvalstate:'wt->'wState.tvaltiming_wheel_length:_t->int(** [now t] returns the current time of incremental's clock. *)valnow:_t->Time_ns.t(** [watch_now t] returns an incremental that tracks the current time. *)valwatch_now:'wt->(Time_ns.t,'w)incremental(** [advance_clock t ~to_] moves incremental's clock forward to [to_].
[advance_clock] is a no-op if [to_ < now t]. As with [Var.set], the effect of
[advance_clock] is not seen on incremental values until the next stabilization.
Unlike [Var.set], calling [advance_clock] during stabilization raises.
In certain pathological cases, [advance_clock] can raise due to it detecting a
cycle in the incremental graph. *)valadvance_clock:_t->to_:Time_ns.t->unit(** [advance_clock_by t span = advance_clock t ~to_:(Time_ns.add (now t) span)] *)valadvance_clock_by:_t->Time_ns.Span.t->unit(** [at t time] returns an incremental that is [Before] when [now t < time] and
[After] when [now t >= time]. *)valat:'wt->Time_ns.t->(Before_or_after.t,'w)incremental(** [after t span] is [at t (Time_ns.add (now t) span)]. *)valafter:'wt->Time_ns.Span.t->(Before_or_after.t,'w)incremental(** [at_intervals t interval] returns an incremental whose value changes at time
intervals of the form:
{[
Time_ns.next_multiple ~base ~after ~interval
]}
where [base] is [now t] when [at_intervals] was called and [after] is the current
[now t].
[at_intervals] raises if [interval < alarm_precision]. The [unit t] that
[at_intervals] returns has its cutoff set to [Cutoff.never], so that although its
value is always [()], incrementals that depend on it will refire each time it is
set. The result of [at_intervals] remains alive and is updated until the
left-hand side of its defining bind changes, at which point it becomes invalid. *)valat_intervals:'wt->Time_ns.Span.t->(unit,'w)incremental(** [step_function t ~init [(t1, v1); ...; (tn, vn)]] returns an incremental whose
initial value is [init] and takes on the values [v1], ..., [vn] in sequence taking
on the value [vi] when [now t >= ti].
It is possible for [vi] to be skipped if time advances from [t(i-1)] to some time
greater than [t(i+1)].
The times must be in nondecreasing order, i.e. [step_function] raises if for some
[i < j], [ti > tj]. *)valstep_function:'wt->init:'a->(Time_ns.t*'a)list->('a,'w)incremental(** [incremental_step_function t i] returns an incremental whose value is
[Step_function.value f ~at:(now t)], where [f] is the value of [i]. *)valincremental_step_function:'wt->('aStep_function.t,'w)incremental->('a,'w)incremental(** [snapshot t value_at ~at ~before] returns an incremental whose value is [before]
before [at] and whose value is frozen to the value of [value_at] during the first
stabilization in which the time passes [at]. [snapshot] causes [value_at] to be
necessary during that stabilization even if the [snapshot] node itself is not
necessary, but not thereafter (although of course [value_at] could remain
necessary for other reaspons). The result of [snapshot] will only be invalidated
if [value_at] is invalid at the moment of the snapshot.
[snapshot] returns [Error] if [at < now t], because it is impossible to take the
snapshot because the time has already passed. *)valsnapshot:'wt->('a,'w)incremental->at:Time_ns.t->before:'a->('a,'w)incrementalOr_error.tend(** A low-level, experimental interface to incremental. This is useful when you need
more control over the dependency graph, for performance reasons. It comes at the
cost that it's much harder to use right. Specifically, here is what you can do with
an expert node:
- learn when any child changes, so the expert node can update itself incrementally,
rather than having to look at the value of all its children.
- incrementally update its set of parents.
- select which parents should fire.
If you use this interface, you are most definitely advised to test carefully, and in
particular you should try it out using [incremental_debug], which is going to check
most pre-conditions. *)moduleExpert:sigmoduleDependency:sig(** A [t] represents the edge from a child incremental to a parent expert node. A
[t] is stateful, you cannot use the same [t] to link one child node to multiple
parents at the same time. *)type('a,'w)t[@@derivingsexp_of](** When calling [create ?on_change child], nothing happens until the [t] is linked
to a parent. see [Node.add_dependency] for documentation of [on_change]. *)valcreate:?on_change:('a->unit)->('a,'w)incremental->('a,'w)t(** [value t] reads the value of the child incremental. It can only be used from
the callback of the [Expert.Node.t] that has [t] in its set of dependencies. *)valvalue:('a,_)t->'aendmoduleNode:sigtype('a,'w)t[@@derivingsexp_of](** [let t = create ?on_observability_change callback] creates a new expert node.
[on_observability_change], if given, is called whenever the node becomes
observable or unobservable (with alternating value for [is_now_observable],
starting at [true] the first time the node becomes observable). This callback
could run multiple times per stabilization. It should not change the
incremental graph.
[callback] is called if any dependency of [t] has changed since it was last
called, or if the set of dependencies has changed. The callback will only run
when all the dependencies are up-to-date, and inside the callback, you can
safely call [Dependency.value] on your dependencies, as well as call all the
functions below on the parent nodes. Any behavior that works on all incremental
nodes (cutoff, invalidation, debug info etc) also work on [t]. *)valcreate:'wState.t->?on_observability_change:(is_now_observable:bool->unit)->(unit->'a)->('a,'w)t(** [watch t] allows you to plug [t] in the rest of the incremental graph, but it's
also useful to set a cutoff function, debug info etc. *)valwatch:('a,'w)t->('a,'w)incremental(** Calling [make_stale t] ensures that incremental will recompute [t] before
anyone reads its value. [t] may not fire though, if it never becomes
necessary. This is intended to be called only from a child of [t]. Along with
a well chosen cutoff function, it allows to choose which parents should
fire. *)valmake_stale:_t->unit(** [invalidate t] makes [t] invalid, as if its surrounding bind had changed. This
is intended to be called only from a child of [t]. *)valinvalidate:_t->unit(** [add_dependency t dep] makes [t] depend on the child incremental in the [dep].
If [dep] is already used to link the child incremental to another parent, an
exception is raised.
This is intended to be called either outside of stabilization, or right after
creating [t], or from a child of [t].
The [on_change] callback of [dep] will be fired when [t] becomes observable,
or immediately, or whenever the child changes as long as [t] is observable.
When this function is called due to observability changes, the callback may
fire several times in the same stabilization, so it should be idempotent. The
callback must not change the incremental graph, particularly not the
dependencies of [t].
All the [on_change] callbacks are guaranteed to be run before the callback of
[create] is run. *)valadd_dependency:(_,'w)t->(_,'w)Dependency.t->unit(** [remove_dependency t dep] can only be called from a child of [t]. *)valremove_dependency:(_,'w)t->(_,'w)Dependency.t->unitendmoduleStep_result:sigtypet=|Keep_going|Done[@@derivingsexp_of]end(** [do_one_step_of_stabilize state] runs a part of stabilization. This can be used as
a replacement for [stabilize], to split up potentially long calls to [stabilize].
A stabilization is ongoing until [do_one_step_of_stabilize] returns [Done]. During
such a stabilization, behavior is the same as during a regular stabilization.
Calling [do_one_step_of_stabilize] during a call to [do_one_step_of_stabilize] or
[stabilize] is a programming error but it is not detected. Behavior is
undefined. *)valdo_one_step_of_stabilize:_State.t->Step_result.tendmoduletypeS_gen=S_genmoduletypeS=sigtypestate_witness[@@derivingsexp_of]includeS_genwithtype'at=('a,state_witness)incrementalwithtypeBefore_or_after.t=Before_or_after.twithtypeClock.t=state_witnessClock.twithtype'aCutoff.t='aCutoff.twithtype'aExpert.Dependency.t=('a,state_witness)Expert.Dependency.twithtype'aExpert.Node.t=('a,state_witness)Expert.Node.twithtypeExpert.Step_result.t=Expert.Step_result.twithtype'aObserver.t=('a,state_witness)Observer.twithtype'aObserver.Update.t='aObserver.Update.twithtypePacked.t=Packed.twithtypeScope.t=state_witnessScope.twithtypeState.t=state_witnessState.twithtypeState.Stats.t=State.Stats.twithtype('a,'b)Unordered_array_fold_update.t=('a,'b)Unordered_array_fold_update.twithtype'aUpdate.t='aUpdate.twithtype'aVar.t=('a,state_witness)Var.tend(** [Make] returns a new incremental implementation. [Make] uses [Config.Default
()]. *)moduleMake():SmoduleConfig:Config_intf.ConfigmoduletypeIncremental_config=Config.Incremental_configmoduleMake_with_config(C:Incremental_config)():S(*_ See the Jane Street Style Guide for an explanation of [Private] submodules:
https://opensource.janestreet.com/standards/#private-submodules *)modulePrivate:sigvaldebug:boolendend