Module Incremental
include Incremental__.Incremental_intf.Incremental
module State : sig ... end
A
State.t
holds shared state used by all the incremental functions.
type ('a, 'w) t
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
andget_cutoff
. However, if you have typesa1
anda2
wherea1
is 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.t
val is_const : (_, _) t -> bool
If
is_const t
thent
is a constant-valued incremental.is_const (const a)
is true.
Creating incrementals
val const : 'w State.t -> 'a -> ('a, 'w) t
const state a
returns 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) t
val map : ('a, 'w) t -> f:('a -> 'b) -> ('b, 'w) t
map t1 ~f
returns an incrementalt
that maintains its value asf a
, wherea
is the value oft1
.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 seearray_fold
andunordered_array_fold
.f
should not create incremental nodes but this behavior is not checked; if you want to create incremental nodes, usebind
. The invalidation machinery that is used withbind
is not used withmap
.
val (>>|) : ('a, 'w) t -> ('a -> 'b) -> ('b, 'w) t
val map2 : ('a1, 'w) t -> ('a2, 'w) t -> f:('a1 -> 'a2 -> 'b) -> ('b, 'w) t
val map3 : ('a1, 'w) t -> ('a2, 'w) t -> ('a3, 'w) t -> f:('a1 -> 'a2 -> 'a3 -> 'b) -> ('b, 'w) t
val map4 : ('a1, 'w) t -> ('a2, 'w) t -> ('a3, 'w) t -> ('a4, 'w) t -> f:('a1 -> 'a2 -> 'a3 -> 'a4 -> 'b) -> ('b, 'w) t
val 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) t
val 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) t
val 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) t
val 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) t
val 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) t
val 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) t
val 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) t
val 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) t
val 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) t
val 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) t
val 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) t
bind t1 ~f
returns an incrementalt2
that behaves likef v
, wherev
is the value oft1
. Ift1
's value changes, then incremental appliesf
to that new value andt2
behaves like the resulting incremental.bind
can be significantly more expensive thanmap
during stabilization, because, when its left-hand side changes, it requires modification of the incremental DAG, whilemap
simply flows values along the DAG. Thus it is preferable to usemap
(and its n-ary variants above) instead ofbind
unless one actually needsbind
'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 otherbind<N>
functions are generalize to more arguments.
val (>>=) : ('a, 'w) t -> ('a -> ('b, 'w) t) -> ('b, 'w) t
val bind2 : ('a1, 'w) t -> ('a2, 'w) t -> f:('a1 -> 'a2 -> ('b, 'w) t) -> ('b, 'w) t
val bind3 : ('a1, 'w) t -> ('a2, 'w) t -> ('a3, 'w) t -> f:('a1 -> 'a2 -> 'a3 -> ('b, 'w) t) -> ('b, 'w) t
val 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 ... end
val join : (('a, 'w) t, 'w) t -> ('a, 'w) t
join t
returns an incremental that behaves like the incremental thatt
currently holds.
val if_ : (bool, 'w) t -> then_:('a, 'w) t -> else_:('a, 'w) t -> ('a, 'w) t
if_ tb ~then_ ~else_
returns an incrementalt
that holds the value ofthen_
iftb
is true, the value ofelse_
iftb
is false. Note thatt
only depends on one ofthen_
orelse_
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_)
val freeze : ?when_:('a -> bool) -> ('a, 'w) t -> ('a, 'w) t
freeze ?when_ t
returns an incremental whose value ist
's valuev
until the first stabilization in whichwhen_ v
holds, at which point the freeze node's value becomes constant and never changes again. Callingfreeze t
forcest
to be necessary until it freezes regardless of whether the freeze node is necessary, but not thereafter (although of courset
could remain necessary for other reasons). The result offreeze t
, once frozen, will never be invalidated, even ift
is invalidated, and even if the scope in which the freeze is created is invalidated. However, prior towhen_ v
becoming true,freeze t
can be invalidated.
val depend_on : ('a, 'w) t -> depend_on:(_, 'w) t -> ('a, 'w) t
depend_on input ~depend_on
returns anoutput
whose value is the same asinput
's value, such thatdepend_on
is necessary so long asoutput
is 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) t
necessary_if_alive input
returnsoutput
that has the same value and cutoff asinput
, such that as long asoutput
is alive,input
is necessary.
val for_all : 'w State.t -> (bool, 'w) t array -> (bool, 'w) t
for_all ts
returns an incremental that istrue
iff allts
aretrue
.
val exists : 'w State.t -> (bool, 'w) t array -> (bool, 'w) t
exists ts
returns an incremental that istrue
iff at least one of thets
istrue
.
val all : 'w State.t -> ('a, 'w) t list -> ('a list, 'w) t
all ts
returns an incremental whose value is a list of the values of all of thets
. In any stabilization where any of thets
changes, the entire list is recreated (once all of thets
have stabilized). This essentially anarray_fold
over thets
.
val both : ('a, 'w) t -> ('b, 'w) t -> ('a * 'b, 'w) t
both t1 t2
returns an incremental whose value is pair of values oft1
andt2
. Bothmap (both t1 t2) ~f
andmap2 t1 t2 ~f:(fun a1 a2 -> f (a1, a2))
return an incremental with the same behavior, but themap2
version is more efficient, because it creates a single node, whereas theboth
version 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) t
array_fold ts ~init ~f
creates an incrementalt
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 thets
have been computed.
val reduce_balanced : 'w State.t -> ('a, 'w) t array -> f:('a -> 'b) -> reduce:('b -> 'b -> 'b) -> ('b, 'w) t option
reduce_balanced ts ~f ~reduce
does a fold-like operation overts
. Unlikearray_fold
, the operation will be computed inO(min(n, k * log(n)))
time, wheren
is the size ofts
andk
is the number of elements ofts
that have changed since the last stabilization.Generally, if most or all of
ts
are changing between stabilizations, usingarray_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.
module Unordered_array_fold_update : sig ... end
val 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) t
unordered_array_fold ts ~init ~f ~update
folds over thets
. Unlikearray_fold
, the fold will be computed in time proportional to the number ofts
that change rather than the number ofts
. In a stabilization, for eacht
ints
that changes fromold_value
tonew_value
, the value of the unordered-array fold,b
, will change depending onupdate
:F_inverse f_inverse
: fromb
tof (f_inverse b old_value) new_value
Update update
: fromb
toupdate 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 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) t
opt_unordered_array_fold
is likeunordered_array_fold
, except that its result isSome
iff 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) t
sum ts ~zero ~add ~sub ?full_compute_every_n_changes
returns an incremental that maintains the sum of thets
. It usesunordered_array_fold
so that the work required to maintain the sum is proportional to the number ofts
that change (i.e. onesub
and oneadd
per change).opt_sum
is likesum
, except that its result isSome
iff 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) t
val sum_int : 'w State.t -> (int, 'w) t array -> (int, 'w) t
sum_int ts
=sum ts ~zero:0 ~add:(+) ~sub:(-)
val sum_float : 'w State.t -> (float, 'w) t array -> (float, '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 everylength ts
changes to cut down on floating-point error.
module Scope : sig ... end
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:
module Var : sig ... end
module Observer : sig ... end
val observe : ?should_finalize:bool -> ('a, 'w) t -> ('a, 'w) Observer.t
observe t
returns a new observer fort
.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 untildisallow_future_use
is explicitly called.
module Update : sig ... end
val on_update : ('a, _) t -> f:('a Update.t -> unit) -> unit
on_update t ~f
is similar toObserver.on_update_exn
, but it does not causet
to be necessary. Instead of theInitialized
update, there are updates for when a node becomesNecessary
orUnnecessary
. 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
t
gets a new value during a stabilization but is unnecessary at the end of it,f
will _not_ be called withChanged
, but withUnnecessary
if allowed by the transition diagram. I.e. if the prior call tof
was withNecessary
orChanged
,f
will be called withUnnecessary
. If the prior call tof
was withInvalidated
orUnnecessary
, thenf
will not be called.One should typically use
Observer.on_update_exn
, unless theUnnecessary
updates are needed.
Stabilization
val stabilize : _ State.t -> unit
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.
val am_stabilizing : _ State.t -> bool
Cutoffs
module Cutoff : sig ... end
An
'a Cutoff.t
is a function that returnstrue
if 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 -> unit
set_cutoff t cutoff
replaces the current cutoff function fort
withcutoff
.cutoff
will be called any timet
is recomputed, withold_value
being the value oft
before the recomputation andnew_value
being the value that just recomputed. Ifcutoff ~old_value ~new_value
, thent
's value will remain asold_value
(new_value
is discarded) and anything depending ont
will not be recomputed (at least not because oft
). Ifnot (cutoff ~old_value ~new_value)
, thent
's value will becomenew_value
, and all nodes depending ont
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 aunit incremental
would only fire once; to disable this, useset_cutoff t Cutoff.never
.
val get_cutoff : ('a, _) t -> 'a Cutoff.t
val lazy_from_fun : _ State.t -> (unit -> 'a) -> 'a Core_kernel.Lazy.t
lazy_from_fun f
is likeLazy.from_fun f
, except that the nodes created byf
will be created in the scope in whichlazy_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, alllazy e
that might create incremental nodes should be replaced bylazy_from_fun (fun () -> e)
.As usual with
Lazy
, iff
raises, then that exception will be raised when callingLazy.force
.
val default_hash_table_initial_size : int
val memoize_fun : ?initial_size:int -> _ State.t -> 'a Base.Hashtbl.Key.t -> ('a -> 'b) -> ('a -> 'b) Core_kernel.Staged.t
memoize_fun f hashable
returns a functionm
that is a memoized version off
that will runf a
on each distincta
thatm
is applied to, memoize the result (in a hash table), and thereafter fora
,m
will return the memoized result.When
m
is called, it usesScope.within
to runf
in the scope that was in effect whenmemoize_fun f
was called. This is essential to correctly capture the dependence of nodes thatf
creates on values thatf
is closed over, which may in turn depend on the left-hand sides of binds in the scope in effect whenmemoize_fun f
was called. Furthermore, nodes thatf
creates do not depend on the scope in effect whenm
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.
val memoize_fun_by_key : ?initial_size:int -> _ State.t -> 'key Base.Hashtbl.Key.t -> ('a -> 'key) -> ('a -> 'b) -> ('a -> 'b) Core_kernel.Staged.t
val 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.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.
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.t
val user_info : (_, _) t -> Core_kernel.Info.t option
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.
val set_user_info : (_, _) t -> Core_kernel.Info.t option -> unit
module Packed : sig ... end
val pack : (_, _) t -> Packed.t
val save_dot : _ State.t -> string -> unit
save_dot file
outputs tofile
the DAG of all necessary nodes, in dot format.
module Let_syntax : sig ... end
This
Let_syntax
allows you to write expressions like
module Before_or_after : sig ... end
module Step_function = Incremental__.Import.Step_function
module Clock : sig ... end
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 Expert : sig ... end
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:
module type S_gen = Incremental__.Incremental_intf.S_gen
module type S = sig ... end
module Make : functor () S
Make
returns a new incremental implementation.Make
usesConfig.Default ()
.
module Config : Incremental__.Config_intf.Config
module type Incremental_config = Config.Incremental_config
module Make_with_config : functor (C : Incremental_config) -> functor () S
module Private : sig ... end