Module Incremental
include Incremental__.Incremental_intf.Incremental
module State : sig ... endA
State.tholds shared state used by all the incremental functions.
type ('a, 'w) ttype ('a,'w) tis 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_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) -> ('w -> Ppx_sexp_conv_lib.Sexp.t) -> ('a, 'w) t -> Ppx_sexp_conv_lib.Sexp.t
type ('a, 'w) incremental:= ('a, 'w) t
include Core_kernel.Invariant.S2 with type ('a, 'w) t := ('a, 'w) t
val invariant : 'a Base__.Invariant_intf.inv -> 'b Base__.Invariant_intf.inv -> ('a, 'b) t Base__.Invariant_intf.inv
val state : (_, 'w) t -> 'w State.tval is_const : (_, _) t -> boolIf
is_const tthentis a constant-valued incremental.is_const (const a)is true.
Creating incrementals
val const : 'w State.t -> 'a -> ('a, 'w) tconst state areturns 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 : 'w State.t -> 'a -> ('a, 'w) tval map : ('a, 'w) t -> f:('a -> 'b) -> ('b, 'w) 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.
include Incremental__.Incremental_intf.Map_n_gen with type ('a, 'w) t := ('a, 'w) t
val map2 : ('a1, 'w) t -> ('a2, 'w) t -> f:('a1 -> 'a2 -> 'b) -> ('b, 'w) tval map3 : ('a1, 'w) t -> ('a2, 'w) t -> ('a3, 'w) t -> f:('a1 -> 'a2 -> 'a3 -> 'b) -> ('b, 'w) tval map4 : ('a1, 'w) t -> ('a2, 'w) t -> ('a3, 'w) t -> ('a4, 'w) t -> f:('a1 -> 'a2 -> 'a3 -> 'a4 -> 'b) -> ('b, 'w) tval map5 : ('a1, 'w) t -> ('a2, 'w) t -> ('a3, 'w) t -> ('a4, 'w) t -> ('a5, 'w) t -> f:('a1 -> 'a2 -> 'a3 -> 'a4 -> 'a5 -> 'b) -> ('b, 'w) tval map6 : ('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) tval map7 : ('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) tval map8 : ('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) tval map9 : ('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) tval map10 : ('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) tval map11 : ('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) tval map12 : ('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) tval map13 : ('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) tval map14 : ('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) tval map15 : ('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) t
val bind : ('a, 'w) t -> f:('a -> ('b, 'w) t) -> ('b, 'w) 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.
include Incremental__.Incremental_intf.Bind_n_gen with type ('a, 'w) t := ('a, 'w) t
val bind2 : ('a1, 'w) t -> ('a2, 'w) t -> f:('a1 -> 'a2 -> ('b, 'w) t) -> ('b, 'w) tval bind3 : ('a1, 'w) t -> ('a2, 'w) t -> ('a3, 'w) t -> f:('a1 -> 'a2 -> 'a3 -> ('b, 'w) t) -> ('b, 'w) tval bind4 : ('a1, 'w) t -> ('a2, 'w) t -> ('a3, 'w) t -> ('a4, 'w) t -> f:('a1 -> 'a2 -> 'a3 -> 'a4 -> ('b, 'w) t) -> ('b, 'w) t
module Infix : sig ... endval join : (('a, 'w) t, 'w) t -> ('a, 'w) tjoin treturns an incremental that behaves like the incremental thattcurrently holds.
val if_ : (bool, 'w) t -> then_:('a, 'w) t -> else_:('a, 'w) t -> ('a, 'w) 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, 'w) t -> ('a, 'w) 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, 'w) t -> depend_on:(_, 'w) t -> ('a, 'w) 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, 'w) t -> ('a, 'w) tnecessary_if_alive inputreturnsoutputthat has the same value and cutoff asinput, such that as long asoutputis alive,inputis necessary.
val for_all : 'w State.t -> (bool, 'w) t array -> (bool, 'w) tfor_all tsreturns an incremental that istrueiff alltsaretrue.
val exists : 'w State.t -> (bool, 'w) t array -> (bool, 'w) texists tsreturns an incremental that istrueiff at least one of thetsistrue.
val all : 'w State.t -> ('a, 'w) t list -> ('a list, 'w) 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, 'w) t -> ('b, 'w) t -> ('a * 'b, 'w) 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 : 'w State.t -> ('a, 'w) t array -> init:'b -> f:('b -> 'a -> 'b) -> ('b, 'w) 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 : 'w State.t -> ('a, 'w) t array -> f:('a -> 'b) -> reduce:('b -> 'b -> 'b) -> ('b, 'w) 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.
module Unordered_array_fold_update : sig ... endval unordered_array_fold : 'w State.t -> ?full_compute_every_n_changes:int -> ('a, 'w) t array -> init:'b -> f:('b -> 'a -> 'b) -> update:('a, 'b) Unordered_array_fold_update.t -> ('b, 'w) tunordered_array_fold ts ~init ~f ~updatefolds 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,b, will change depending onupdate:F_inverse f_inverse: frombtof (f_inverse b old_value) new_valueUpdate update: frombtoupdate 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_changeschanges. If you do not supplyfull_compute_every_n_changes, then full computes will never happen after the initial one.
val opt_unordered_array_fold : 'w State.t -> ?full_compute_every_n_changes:int -> ('a option, 'w) t array -> init:'b -> f:('b -> 'a -> 'b) -> f_inverse:('b -> 'a -> 'b) -> ('b option, 'w) topt_unordered_array_foldis likeunordered_array_fold, except that its result isSomeiff all its inputs areSome.
val sum : 'w State.t -> ?full_compute_every_n_changes:int -> ('a, 'w) t array -> zero:'a -> add:('a -> 'a -> 'a) -> sub:('a -> 'a -> 'a) -> ('a, 'w) 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.
val opt_sum : 'w State.t -> ?full_compute_every_n_changes:int -> ('a option, 'w) t array -> zero:'a -> add:('a -> 'a -> 'a) -> sub:('a -> 'a -> 'a) -> ('a option, 'w) tval sum_int : 'w State.t -> (int, 'w) t array -> (int, 'w) tsum_int ts=sum ts ~zero:0 ~add:(+) ~sub:(-)
val sum_float : 'w State.t -> (float, 'w) t array -> (float, 'w) tsum_float tsis:sum ts ~zero:0.0 ~add:(+.) ~sub:(-.) ~full_compute_every_n_changes:(Array.length ts)This uses
sumfor fast update, with a full recompute everylength tschanges to cut down on floating-point error.
module Scope : sig ... endThe 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:
module Var : sig ... endmodule Observer : sig ... endval observe : ?should_finalize:bool -> ('a, 'w) t -> ('a, 'w) 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 : sig ... endval on_update : ('a, _) t -> f:('a Update.t -> unit) -> uniton_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:/-----------------------------------------------------\ | / | | | v Start ------> Necessary ----------> Changed ------> Invalidated | | ^ | ^ | ^ | | | /---------/ \--/ | | v | v | \----------> Unnecessary -----------------------------/If
tgets a new value during a stabilization but is unnecessary at the end of it,fwill _not_ be called withChanged, but withUnnecessaryif allowed by the transition diagram. I.e. if the prior call tofwas withNecessaryorChanged,fwill be called withUnnecessary. If the prior call tofwas withInvalidatedorUnnecessary, thenfwill not be called.One should typically use
Observer.on_update_exn, unless theUnnecessaryupdates are needed.
Stabilization
val stabilize : _ State.t -> unitstabilize ()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.
val am_stabilizing : _ State.t -> bool
Cutoffs
module Cutoff : sig ... endAn
'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.
val get_cutoff : ('a, _) t -> 'a Cutoff.tval lazy_from_fun : _ State.t -> (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 -> _ State.t -> 'a Base.Hashtbl.Key.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 -> _ State.t -> 'key Base.Hashtbl.Key.t -> ('a -> 'key) -> ('a -> 'b) -> ('a -> 'b) Core_kernel.Staged.tval weak_memoize_fun : ?initial_size:int -> _ State.t -> 'a Base.Hashtbl.Key.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 -> _ State.t -> 'key Base.Hashtbl.Key.t -> ('a -> 'key) -> ('a -> 'b Core_kernel.Heap_block.t) -> ('a -> 'b Core_kernel.Heap_block.t) 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 Node_value : sig ... endval node_value : ('a, _) t -> 'a Node_value.t
module Packed : sig ... endval pack : (_, _) t -> Packed.tval save_dot : _ State.t -> string -> unitsave_dot fileoutputs tofilethe DAG of all necessary nodes, in dot format.
module Let_syntax : sig ... endThis
Let_syntaxallows you to write expressions like
module Before_or_after : sig ... endmodule Step_function = Incremental__.Import.Step_functionmodule Clock : sig ... endIncremental has a timing-wheel-based clock, and lets one build incremental values that change as its time passes. One must explicitly call
advance_clockto change incremental's clock; there is no implicit call based on the passage of time.
module Expert : sig ... endA 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 type S_gen = Incremental__.Incremental_intf.S_genmodule type S = sig ... endmodule Make : functor () SMakereturns a new incremental implementation.MakeusesConfig.Default ().
module Config : Incremental__.Config_intf.Configmodule type Incremental_config = Config.Incremental_configmodule Make_with_config : functor (C : Incremental_config) -> functor () Smodule Private : sig ... end