Module Incr_dom.Incr
module Incr : Incremental.Sval clock : Incr.Clock.t
include Incr
type 'a ttype 'a tis the type of incrementals that have a value of type'a.Incrementals are not covariant, i.e. we do not have
+'a t-- consider, e.g.set_cutoffandget_cutoff. However, if you have typesa1anda2wherea1is a subtype ofa2, and a valuet1 : a1 t, then the following builds an incremental value of typea2 t:let t2 : a2 t = t1 >>| fun a1 -> (a1 : a1 :> a2)
val sexp_of_t : ('a -> Ppx_sexp_conv_lib.Sexp.t) -> 'a t -> Ppx_sexp_conv_lib.Sexp.t
type 'a incremental= 'a t
include Core_kernel.Invariant.S1 with type 'a t := 'a t
val invariant : 'a Base__.Invariant_intf.inv -> 'a t Base__.Invariant_intf.inv
val is_const : _ t -> boolIf
is_const tthentis a constant-valued incremental.is_const (const a)is true.
Creating incrementals
val const : 'a -> 'a tconst vreturns an incremental whose value never changes. It is the same asreturn, but reads more clearly in many situations because it serves as a nice reminder that the incremental won't change (except possibly be invalidated).
val return : 'a -> 'a tval map : 'a t -> f:('a -> 'b) -> 'b tmap t1 ~freturns an incrementaltthat maintains its value asf a, whereais the value oft1.map2,map3, ...,map9are the generalizations to more arguments. If you need map<N> for some N > 9, it can easily be added, but also seearray_foldandunordered_array_fold.fshould not create incremental nodes but this behavior is not checked; if you want to create incremental nodes, usebind. The invalidation machinery that is used withbindis not used withmap.
val (>>|) : 'a t -> ('a -> 'b) -> 'b tval map2 : 'a1 t -> 'a2 t -> f:('a1 -> 'a2 -> 'b) -> 'b tval map3 : 'a1 t -> 'a2 t -> 'a3 t -> f:('a1 -> 'a2 -> 'a3 -> 'b) -> 'b tval map4 : 'a1 t -> 'a2 t -> 'a3 t -> 'a4 t -> f:('a1 -> 'a2 -> 'a3 -> 'a4 -> 'b) -> 'b tval map5 : 'a1 t -> 'a2 t -> 'a3 t -> 'a4 t -> 'a5 t -> f:('a1 -> 'a2 -> 'a3 -> 'a4 -> 'a5 -> 'b) -> 'b tval map6 : 'a1 t -> 'a2 t -> 'a3 t -> 'a4 t -> 'a5 t -> 'a6 t -> f:('a1 -> 'a2 -> 'a3 -> 'a4 -> 'a5 -> 'a6 -> 'b) -> 'b tval map7 : 'a1 t -> 'a2 t -> 'a3 t -> 'a4 t -> 'a5 t -> 'a6 t -> 'a7 t -> f:('a1 -> 'a2 -> 'a3 -> 'a4 -> 'a5 -> 'a6 -> 'a7 -> 'b) -> 'b tval map8 : 'a1 t -> 'a2 t -> 'a3 t -> 'a4 t -> 'a5 t -> 'a6 t -> 'a7 t -> 'a8 t -> f:('a1 -> 'a2 -> 'a3 -> 'a4 -> 'a5 -> 'a6 -> 'a7 -> 'a8 -> 'b) -> 'b tval map9 : 'a1 t -> 'a2 t -> 'a3 t -> 'a4 t -> 'a5 t -> 'a6 t -> 'a7 t -> 'a8 t -> 'a9 t -> f:('a1 -> 'a2 -> 'a3 -> 'a4 -> 'a5 -> 'a6 -> 'a7 -> 'a8 -> 'a9 -> 'b) -> 'b tval bind : 'a t -> f:('a -> 'b t) -> 'b tbind t1 ~freturns an incrementalt2that behaves likef v, wherevis the value oft1. Ift1's value changes, then incremental appliesfto that new value andt2behaves like the resulting incremental.bindcan be significantly more expensive thanmapduring stabilization, because, when its left-hand side changes, it requires modification of the incremental DAG, whilemapsimply flows values along the DAG. Thus it is preferable to usemap(and its n-ary variants above) instead ofbindunless one actually needsbind's power.bind2 t1 t2 ~fis: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 otherbind<N>functions are generalize to more arguments.
val (>>=) : 'a t -> ('a -> 'b t) -> 'b tval bind2 : 'a1 t -> 'a2 t -> f:('a1 -> 'a2 -> 'b t) -> 'b tval bind3 : 'a1 t -> 'a2 t -> 'a3 t -> f:('a1 -> 'a2 -> 'a3 -> 'b t) -> 'b tval bind4 : 'a1 t -> 'a2 t -> 'a3 t -> 'a4 t -> f:('a1 -> 'a2 -> 'a3 -> 'a4 -> 'b t) -> 'b t
module Infix = Incr.Infixval join : 'a t t -> 'a tjoin treturns an incremental that behaves like the incremental thattcurrently holds.
val if_ : bool t -> then_:'a t -> else_:'a t -> 'a tif_ tb ~then_ ~else_returns an incrementaltthat holds the value ofthen_iftbis true, the value ofelse_iftbis false. Note thattonly depends on one ofthen_orelse_at a time, i.e.if_ tb ~then_ ~elseis 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_)
val freeze : ?when_:('a -> bool) -> 'a t -> 'a tfreeze ?when_ treturns an incremental whose value ist's valuevuntil the first stabilization in whichwhen_ vholds, at which point the freeze node's value becomes constant and never changes again. Callingfreeze tforcestto be necessary until it freezes regardless of whether the freeze node is necessary, but not thereafter (although of coursetcould remain necessary for other reasons). The result offreeze t, once frozen, will never be invalidated, even iftis invalidated, and even if the scope in which the freeze is created is invalidated. However, prior towhen_ vbecoming true,freeze tcan be invalidated.
val depend_on : 'a t -> depend_on:_ t -> 'a tdepend_on input ~depend_onreturns anoutputwhose value is the same asinput's value, such thatdepend_onis necessary so long asoutputis necessary. It is like:map2 input depend_on ~f:(fun a _ -> a)but with a cutoff such that
output's value only changes wheninput's value changes.
val necessary_if_alive : 'a t -> 'a tnecessary_if_alive inputreturnsoutputthat has the same value and cutoff asinput, such that as long asoutputis alive,inputis necessary.
val for_all : bool t array -> bool tfor_all tsreturns an incremental that istrueiff alltsaretrue.
val exists : bool t array -> bool texists tsreturns an incremental that istrueiff at least one of thetsistrue.
val all : 'a t list -> 'a list tall tsreturns an incremental whose value is a list of the values of all of thets. In any stabilization where any of thetschanges, the entire list is recreated (once all of thetshave stabilized). This essentially anarray_foldover thets.
val both : 'a t -> 'b t -> ('a * 'b) tboth t1 t2returns an incremental whose value is pair of values oft1andt2. Bothmap (both t1 t2) ~fandmap2 t1 t2 ~f:(fun a1 a2 -> f (a1, a2))return an incremental with the same behavior, but themap2version is more efficient, because it creates a single node, whereas thebothversion creates two nodes.
Array folds and sums
val array_fold : 'a t array -> init:'b -> f:('b -> 'a -> 'b) -> 'b tarray_fold ts ~init ~fcreates an incrementaltwhose value is:Array.fold ts ~init ~f:(fun ac t -> f ac (value t))In a stabilization during which any of the
tschanges, the entire fold will be computed once all of thetshave been computed.
val reduce_balanced : 'a t array -> f:('a -> 'b) -> reduce:('b -> 'b -> 'b) -> 'b t optionreduce_balanced ts ~f ~reducedoes a fold-like operation overts. Unlikearray_fold, the operation will be computed inO(min(n, k * log(n)))time, wherenis the size oftsandkis the number of elements oftsthat have changed since the last stabilization.Generally, if most or all of
tsare changing between stabilizations, usingarray_foldwill have better constant factors.The
reduceargument must be an associative operation:reduce a (reduce b c) = (reduce (reduce a b) c).Noneis returned upon supplying an empty array.
val unordered_array_fold : ?full_compute_every_n_changes:int -> 'a t array -> init:'b -> f:('b -> 'a -> 'b) -> f_inverse:('b -> 'a -> 'b) -> 'b tunordered_array_fold ts ~init ~f ~f_inversefolds over thets. Unlikearray_fold, the fold will be computed in time proportional to the number oftsthat change rather than the number ofts. In a stabilization, for eachtintsthat changes fromold_valuetonew_value, the value of the unordered-array fold will change frombtof (f_inverse b old_value) new_value. Thet'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_changeschanges. If you do not supplyfull_compute_every_n_changes, then full computes will never happen after the initial one.opt_unordered_array_foldis likeunordered_array_fold, except that its result isSomeiff all its inputs areSome.
val opt_unordered_array_fold : ?full_compute_every_n_changes:int -> 'a option t array -> init:'b -> f:('b -> 'a -> 'b) -> f_inverse:('b -> 'a -> 'b) -> 'b option tval sum : ?full_compute_every_n_changes:int -> 'a t array -> zero:'a -> add:('a -> 'a -> 'a) -> sub:('a -> 'a -> 'a) -> 'a tsum ts ~zero ~add ~sub ?full_compute_every_n_changesreturns an incremental that maintains the sum of thets. It usesunordered_array_foldso that the work required to maintain the sum is proportional to the number oftsthat change (i.e. onesuband oneaddper change).opt_sumis likesum, except that its result isSomeiff all its inputs areSome.
Variables
module Var = Incr.VarObservers
module Observer = Incr.ObserverAn observer lets one get the value of an incremental, either by asking directly for the value or installing an on-update handler to run when the incremental's value changes.
val observe : ?should_finalize:bool -> 'a t -> 'a Observer.tobserve treturns a new observer fort.observeraises if called during stabilization.By default, an observer has a finalizer that calls
disallow_future_usewhen the observer is no longer referenced. One can use~should_finalize:falseto cause the finalizer to not be created, in which case the observer will live untildisallow_future_useis explicitly called.
module Update = Incr.Updateon_update t ~fis similar toObserver.on_update_exn, but it does not causetto be necessary. Instead of theInitializedupdate, there are updates for when a node becomesNecessaryorUnnecessary. Here is a state diagram for the allowable sequences ofUpdate.t's that can be supplied to a particularf:
Stabilization
Cutoffs
module Cutoff = Incr.CutoffAn
'a Cutoff.tis a function that returnstrueif propagation of changes should be cutoff at a node based on the old value and the (possible) new value of the node.
val set_cutoff : 'a t -> 'a Cutoff.t -> unitset_cutoff t cutoffreplaces the current cutoff function fortwithcutoff.cutoffwill be called any timetis recomputed, withold_valuebeing the value oftbefore the recomputation andnew_valuebeing the value that just recomputed. Ifcutoff ~old_value ~new_value, thent's value will remain asold_value(new_valueis discarded) and anything depending ontwill not be recomputed (at least not because oft). Ifnot (cutoff ~old_value ~new_value), thent's value will becomenew_value, and all nodes depending ontwill recomputed.A reasonable choice for
cutoffis an equality function on'a.The default cutoff for every node is
phys_equal. For example, this means that aunit incrementalwould only fire once; to disable this, useset_cutoff t Cutoff.never.
module Scope = Incr.Scope
val lazy_from_fun : (unit -> 'a) -> 'a Core_kernel.Lazy.tlazy_from_fun fis likeLazy.from_fun f, except that the nodes created byfwill be created in the scope in whichlazy_from_funwas 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, alllazy ethat might create incremental nodes should be replaced bylazy_from_fun (fun () -> e).As usual with
Lazy, iffraises, then that exception will be raised when callingLazy.force.
val default_hash_table_initial_size : intval memoize_fun : ?initial_size:int -> 'a Core_kernel.Hashtbl.Hashable.t -> ('a -> 'b) -> ('a -> 'b) Core_kernel.Staged.tmemoize_fun f hashablereturns a functionmthat is a memoized version offthat will runf aon each distinctathatmis applied to, memoize the result (in a hash table), and thereafter fora,mwill return the memoized result.When
mis called, it usesScope.withinto runfin the scope that was in effect whenmemoize_fun fwas called. This is essential to correctly capture the dependence of nodes thatfcreates on values thatfis closed over, which may in turn depend on the left-hand sides of binds in the scope in effect whenmemoize_fun fwas called. Furthermore, nodes thatfcreates do not depend on the scope in effect whenmis called.memoize_fun_by_keyis a generalization that allows one to memoize over values that contain a uniquely identifying key, but also have other data.
val memoize_fun_by_key : ?initial_size:int -> 'key Core_kernel.Hashtbl.Hashable.t -> ('a -> 'key) -> ('a -> 'b) -> ('a -> 'b) Core_kernel.Staged.tval user_info : _ t -> Core_kernel.Info.t optionFor debugging purposes, one can store an arbitrary
Info.tin a node. This will be displayed as part of a node in error messages.
val set_user_info : _ t -> Core_kernel.Info.t option -> unit
module Expert = Incr.ExpertA 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:
module State = Incr.State
module Packed = Incr.Packedval pack : _ t -> Packed.tval save_dot : string -> unitsave_dot fileoutputs tofilethe DAG of all necessary nodes, in dot format.
val keep_node_creation_backtrace : bool Core_kernel.refIf
keep_node_creation_backtrace, then whenever a new node is created, incremental will callBacktrace.getand store the result in the node. The backtrace will then appear in subsequent error messages when the node is pretty printed.
module Let_syntax = Incr.Let_syntaxThis
Let_syntaxallows you to write expressions like
Time
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.
module Before_or_after = Incr.Before_or_aftermodule Clock = Incr.Clockval weak_memoize_fun : ?initial_size:int -> 'a Core_kernel.Hashtbl.Hashable.t -> ('a -> 'b Core_kernel.Heap_block.t) -> ('a -> 'b Core_kernel.Heap_block.t) Core_kernel.Staged.tThe weak versions of the memoization functions use a
Weak_hashtblfor the memo table. This keeps a weak pointer to each result, and so the garbage collector automatically removes unused results. Furthermore,stabilizeremoves the table entries whose result is unused.
val weak_memoize_fun_by_key : ?initial_size:int -> 'key Core_kernel.Hashtbl.Hashable.t -> ('a -> 'key) -> ('a -> 'b Core_kernel.Heap_block.t) -> ('a -> 'b Core_kernel.Heap_block.t) Core_kernel.Staged.t
module Map : sig ... endmodule Select : sig ... end