Documentation
¶
Overview ¶
Package incremental is a Go port of Jane Street's Incremental, a library for self-adjusting computation: build a DAG of nodes (Var, Map/MapN, Bind, Join, IfThenElse, Freeze, ArrayFold, UnorderedArrayFold, ReduceBalanced, At/AtIntervals, Snapshot, StepFunction, Expert...), Observe the nodes you care about, and call Stabilize to bring every observed node's value up to date with whatever Vars changed since the last round - recomputing only the nodes actually affected.
Purity and determinism contract ¶
Every node's compute function is assumed pure: given the same inputs, it always produces the same output, with no observable side effects other than its return value. This is a hard contract, not a suggestion - the engine relies on it for correctness, not just performance.
Concurrency model ¶
Unlike the OCaml original's single-threaded, height-ordered recompute heap, this port stabilizes with a wavefront dataflow scheduler: each dirty node tracks an atomic count of not-yet-settled recompute dependencies, and becomes eligible for a worker pool the moment that count reaches zero, so independent nodes recompute concurrently. Because compute functions are pure, the result of a round is identical to what a serial, height-ordered recompute would have produced, regardless of how the scheduler happened to interleave work. Dispatching costs more than a cheap node saves, so the engine measures per-node recompute cost and keeps such rounds serial by itself.
State.Stabilize is non-blocking: it queues a round and returns a *Future immediately, so a caller never blocks on recompute. State.StabilizeAndWait blocks until that round (and any rounds queued ahead of it) complete - the common choice for tests and simple call sites. All graph mutations - AddRoot/RemoveRoot, Var.Set, update listeners - are serialized through a single scheduler goroutine, so the graph is never observed in a half-mutated state, without requiring coarse locking around it.
Getting started ¶
s := incremental.NewState(0) // 0 -> serial; pass a count for concurrent recompute
defer s.Close()
x := incremental.NewVar(s, 1)
y := incremental.NewVar(s, 2)
sum := incremental.Map2(x.Watch(), y.Watch(), func(a, b int) int { return a + b }, incremental.NeverCutoff[int]())
obs := incremental.Observe(s, sum)
defer obs.Unobserve()
if err := s.StabilizeAndWait(); err != nil {
// handle err
}
v, _ := obs.Value() // 3
x.Set(10)
_ = s.StabilizeAndWait()
v, _ = obs.Value() // 12
Example ¶
Example shows the core loop: wrap inputs in Vars, derive with combinators, Observe what you care about, and Stabilize after changes - only the affected subgraph recomputes.
package main
import (
"fmt"
incr "github.com/PrositAS/go-incremental"
)
func main() {
s := incr.NewState(0) // 0 -> GOMAXPROCS workers
defer s.Close()
price := incr.NewVar(s, 100.0)
qty := incr.NewVar(s, 3.0)
total := incr.Map2(price.Watch(), qty.Watch(), func(p, q float64) float64 { return p * q })
obs := incr.Observe(s, total)
defer obs.Unobserve()
if err := s.StabilizeAndWait(); err != nil {
panic(err)
}
v, _ := obs.Value()
fmt.Println(v)
price.Set(110)
if err := s.StabilizeAndWait(); err != nil {
panic(err)
}
v, _ = obs.Value()
fmt.Println(v)
}
Output: 300 330
Example (Collections) ¶
Example_collections shows the collection layer: a MapVar of rows flowing through a filter and a group-aggregate, observed as a live view. Each row change propagates as per-key diffs - O(keys changed), never O(rows).
package main
import (
"fmt"
"sort"
incr "github.com/PrositAS/go-incremental"
)
// Example_collections shows the collection layer: a MapVar of rows flowing
// through a filter and a group-aggregate, observed as a live view. Each row
// change propagates as per-key diffs - O(keys changed), never O(rows).
func main() {
s := incr.NewState(0)
defer s.Close()
type Order struct {
Region string
Amount float64
}
orders := incr.NewMapVar[int, Order](s)
big := incr.MapFilter(orders.Watch(), func(_ int, o Order) bool { return o.Amount >= 100 })
byRegion := incr.MapGroupAggregate(big,
func(_ int, o Order) string { return o.Region },
0.0,
func(a float64, o Order) float64 { return a + o.Amount },
func(a float64, o Order) float64 { return a - o.Amount },
)
view := incr.ObserveMap(s, byRegion)
defer view.Unobserve()
orders.Set(1, Order{"eu", 120})
orders.Set(2, Order{"eu", 80}) // below threshold: filtered out
orders.Set(3, Order{"us", 200})
if err := s.StabilizeAndWait(); err != nil {
panic(err)
}
printSorted(view.Snapshot())
orders.Set(2, Order{"eu", 150}) // enters the filter
orders.Delete(3)
if err := s.StabilizeAndWait(); err != nil {
panic(err)
}
printSorted(view.Snapshot())
}
func printSorted(m map[string]float64) {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for i, k := range keys {
if i > 0 {
fmt.Print(" ")
}
fmt.Printf("%s=%g", k, m[k])
}
fmt.Println()
}
Output: eu=120 us=200 eu=270
Index ¶
- Constants
- Variables
- func AddDependency[T, U any](n *ExpertNode[T], dep *Dependency[U])
- func All[T any](inputs []core.ValueNode[T]) core.ValueNode[[]T]
- func AppendUserInfo(node core.AnyNode, info *DotUserInfo)
- func ArrayFold[A, Acc any](children []core.ValueNode[A], init Acc, f func(Acc, A) Acc, ...) core.ValueNode[Acc]
- func AtLeastKOf(inputs []core.ValueNode[bool], k int) core.ValueNode[bool]
- func Bind[A, B any](lhs core.ValueNode[A], f func(A) core.ValueNode[B]) core.ValueNode[B]
- func Both[A, B any](a core.ValueNode[A], b core.ValueNode[B]) core.ValueNode[Pair[A, B]]
- func Const[T any](value T) *core.Node[T]
- func Exists(inputs []core.ValueNode[bool]) core.ValueNode[bool]
- func ForAll(inputs []core.ValueNode[bool]) core.ValueNode[bool]
- func Freeze[T any](child core.ValueNode[T], onlyFreezeWhen func(T) bool) core.ValueNode[T]
- func IfThenElse[T any](test core.ValueNode[bool], thenBranch, elseBranch core.ValueNode[T]) core.ValueNode[T]
- func IncrementalStepFunction[T any](clock *Clock, child core.ValueNode[StepFunctionValue[T]]) core.ValueNode[T]
- func Join[T any](input core.ValueNode[core.ValueNode[T]]) core.ValueNode[T]
- func Map[T, R any](input core.ValueNode[T], f func(T) R, opts ...NodeOpt[R]) core.ValueNode[R]
- func Map2[A1, A2, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], f func(A1, A2) R, ...) core.ValueNode[R]
- func Map3[A1, A2, A3, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], ...) core.ValueNode[R]
- func Map4[A1, A2, A3, A4, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], ...) core.ValueNode[R]
- func Map5[A1, A2, A3, A4, A5, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], ...) core.ValueNode[R]
- func Map6[A1, A2, A3, A4, A5, A6, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], ...) core.ValueNode[R]
- func Map7[A1, A2, A3, A4, A5, A6, A7, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], ...) core.ValueNode[R]
- func Map8[A1, A2, A3, A4, A5, A6, A7, A8, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], ...) core.ValueNode[R]
- func Map9[A1, A2, A3, A4, A5, A6, A7, A8, A9, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], ...) core.ValueNode[R]
- func Map10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], ...) core.ValueNode[R]
- func Map11[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], ...) core.ValueNode[R]
- func Map12[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], ...) core.ValueNode[R]
- func Map13[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], ...) core.ValueNode[R]
- func Map14[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], ...) core.ValueNode[R]
- func Map15[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], ...) core.ValueNode[R]
- func ReduceBalanced[A, Acc any](children []core.ValueNode[A], f func(A) Acc, reduce func(Acc, Acc) Acc, ...) (result core.ValueNode[Acc], ok bool)
- func RemoveDependency[T, U any](n *ExpertNode[T], dep *Dependency[U])
- func SaveDot(roots []core.AnyNode, emitBindEdges bool) string
- func Snapshot[T any](clock *Clock, valueAt core.ValueNode[T], at time.Time, before T) (core.ValueNode[T], error)
- func StepFunction[T any](clock *Clock, init T, steps []Step[T]) core.ValueNode[T]
- func Sum[T any](inputs []core.ValueNode[T], zero T, add func(T, T) T, sub func(T, T) T, ...) core.ValueNode[T]
- func UnorderedArrayFold[A, Acc any](children []core.ValueNode[A], init Acc, f func(Acc, A) Acc, ...) core.ValueNode[Acc]
- type BeforeOrAfter
- type Clock
- func (c *Clock) AdvanceClock(to time.Time) error
- func (c *Clock) AdvanceClockBy(span time.Duration) error
- func (c *Clock) After(span time.Duration) core.ValueNode[BeforeOrAfter]
- func (c *Clock) At(at time.Time) core.ValueNode[BeforeOrAfter]
- func (c *Clock) AtIntervals(interval time.Duration) core.ValueNode[struct{}]
- func (c *Clock) Now() time.Time
- func (c *Clock) WatchNow() core.ValueNode[time.Time]
- type Cutoff
- func AlwaysCutoff[T any]() Cutoff[T]
- func CompareCutoff[T any](compare func(oldValue, newValue T) int) Cutoff[T]
- func EqualCutoff[T any](equal func(oldValue, newValue T) bool) Cutoff[T]
- func NeverCutoff[T any]() Cutoff[T]
- func NewCutoff[T any](f func(oldValue, newValue T) bool) Cutoff[T]
- func PhysEqualCutoff[T comparable]() Cutoff[T]
- type Dependency
- type DotUserInfo
- type ExpertNode
- type Future
- type Kind
- type MapDiff
- type MapNode
- func MapFilter[K comparable, V any](src MapNode[K, V], pred func(K, V) bool) MapNode[K, V]
- func MapGroupAggregate[K comparable, V any, G comparable, A any](src MapNode[K, V], group func(K, V) G, init A, add func(A, V) A, ...) MapNode[G, A]
- func MapGroupMaxBy[K comparable, V any, G comparable, W cmp.Ordered](src MapNode[K, V], group func(K, V) G, by func(K, V) W) MapNode[G, W]
- func MapGroupMinBy[K comparable, V any, G comparable, W cmp.Ordered](src MapNode[K, V], group func(K, V) G, by func(K, V) W) MapNode[G, W]
- func MapGroupReduce[K comparable, V any, G comparable, A any](src MapNode[K, V], group func(K, V) G, init A, combine func(A, V) A) MapNode[G, A]
- func MapJoin[K comparable, L, R, O any](left MapNode[K, L], right MapNode[K, R], combine func(K, L, R) O) MapNode[K, O]
- func MapLeftJoin[K comparable, L, R, O any](left MapNode[K, L], right MapNode[K, R], ...) MapNode[K, O]
- func MapLookupJoin[K comparable, V any, FK comparable, T, O any](src MapNode[K, V], table MapNode[FK, T], on func(K, V) FK, ...) MapNode[K, O]
- func MapValues[K comparable, V, W any](src MapNode[K, V], f func(K, V) W) MapNode[K, W]
- type MapObserver
- type MapVar
- type NodeOpt
- type NodeUpdate
- type NodeUpdateKind
- type Observer
- type Pair
- type State
- type Step
- type StepFunctionValue
- type UnorderedArrayFoldUpdate
- type ValueNode
- type Var
Examples ¶
Constants ¶
const ( // Necessary is delivered the first time a handler observes a node's value: when the // handler is registered, or when the node becomes necessary again after being // Unnecessary. Necessary = core.Necessary // Changed is delivered whenever the node's value actually changes. Changed = core.Changed // Invalidated is delivered once, when the observed node becomes invalid. Invalidated = core.Invalidated // Unnecessary is delivered once, when the observed node stops being necessary. Unnecessary = core.Unnecessary )
Variables ¶
var ErrObserverNotStabilized = errors.New("incremental: observer has no value yet; stabilize first")
ErrObserverNotStabilized is returned by Observer.Value before the observed node has ever been computed by a stabilization round.
var ErrObserverUnlinked = errors.New("incremental: observer use disallowed after Unobserve")
ErrObserverUnlinked is returned by Observer.Value and Observer.OnUpdate after Unobserve.
Functions ¶
func AddDependency ¶
func AddDependency[T, U any](n *ExpertNode[T], dep *Dependency[U])
AddDependency attaches dep as a new child of n and forces n stale so it recomputes with the new dependency in scope - the Go counterpart of Incremental's Expert.Node.add_dependency. If dep's child already has a value and n isn't about to re-fire every callback anyway (see SetObservable), dep.OnChange fires immediately with that value.
func All ¶
All creates a node whose value is the slice of every input's current value, in order - the Go counterpart of Incremental's all.
func AppendUserInfo ¶
func AppendUserInfo(node core.AnyNode, info *DotUserInfo)
AppendUserInfo appends info to node's existing user info, or sets it if node has none yet - the Go counterpart of Incremental's append_user_info_graphviz.
func ArrayFold ¶
func ArrayFold[A, Acc any](children []core.ValueNode[A], init Acc, f func(Acc, A) Acc, opts ...NodeOpt[Acc]) core.ValueNode[Acc]
ArrayFold creates a node whose value folds f over the current values of children in order, starting from init - the Go counterpart of Incremental's array_fold. If children is empty, the result is a Const holding init, mirroring the OCaml original.
func AtLeastKOf ¶
AtLeastKOf creates a node reporting whether at least k of inputs currently hold true - the Go counterpart of Incremental's at_least_k_of. It recomputes in O(1) amortized time per change via UnorderedArrayFold's inverse-update path.
func Bind ¶
Bind creates a node whose right-hand side is rebuilt by calling f every time lhs's value changes - the Go counterpart of Incremental's bind. Each time lhs changes, f runs again against its new value to build a new right-hand side node, which is detached from the graph and replaced by the new one, spliced in in its place.
Unlike the OCaml original, nodes f constructs are not implicitly scoped to the bind's right-hand side: OCaml tracks them via a mutable "current scope" pushed for the duration of f, which is unsafe to share across the concurrently-recomputing goroutines of this engine. Consequently, when a right-hand side is superseded it is only detached - its edge to the bind's main node is removed - and never marked Invalid. This diverges from OCaml, which invalidates and tears down the whole bind scope, and it carries a known cost. A detached right-hand side is unreachable from every root, so it is never recomputed again, but it is NOT necessarily garbage-collected: edges are two-way, so any still-live node it depends on keeps it in that node's parent list. An abandoned right-hand side that references a longer-lived input - the common case, e.g. f building Map(sharedVar, ...) - therefore stays pinned to that input, so a graph that refires binds accumulates one phantom parent per superseded right-hand side on the shared input: an unbounded leak for long-running graphs. We accept this because the safe alternative is worse without scopes: invalidating the old right-hand side (as an earlier version did) permanently breaks the reuse pattern where f returns a pre-existing node - e.g. one of two long-lived nodes chosen by a boolean - since nothing here re-validates a node, and without scopes we cannot tell a fresh intermediate apart from a reused one. Proper teardown needs the bind-scope machinery in internal/core/scope.go, currently unwired because an ambient current-scope is unsafe under this engine's concurrent recompute.
func Both ¶
Both creates a node whose value pairs a and b's current values - the Go counterpart of Incremental's both. If a and b are both Const nodes, the result is itself a Const, mirroring the OCaml original's optimization to avoid an unnecessary Map2 node.
func Const ¶
Const creates a node whose value is fixed at creation and never recomputed - the Go counterpart of Incremental's const. It is never Recomputable: like a Var, it settles immediately whenever the scheduler reaches it, since core.Node's generic IsStale treats "never recomputed" (StabilizationNum none) as the only staleness condition once a value has been assigned this way.
func Exists ¶
Exists creates a node reporting whether any of inputs currently holds true - the Go counterpart of Incremental's exists.
func ForAll ¶
ForAll creates a node reporting whether every one of inputs currently holds true - the Go counterpart of Incremental's for_all.
func Freeze ¶
Freeze creates a node that copies child's value on every recompute until onlyFreezeWhen returns true for that value; from then on it detaches from child and keeps that value forever, so it never recomputes again - the Go counterpart of Incremental's freeze. onlyFreezeWhen defaults to "always true" (freeze on the first stabilization) when nil.
func IfThenElse ¶
func IfThenElse[T any](test core.ValueNode[bool], thenBranch, elseBranch core.ValueNode[T]) core.ValueNode[T]
IfThenElse creates a node whose value tracks thenBranch's or elseBranch's current value, following test - the Go counterpart of Incremental's if_then_else. It switches which branch it depends on (rather than reading both every recompute) whenever test's value changes, so the branch not taken doesn't need to be kept up to date.
func IncrementalStepFunction ¶
func IncrementalStepFunction[T any](clock *Clock, child core.ValueNode[StepFunctionValue[T]]) core.ValueNode[T]
IncrementalStepFunction creates a node whose value follows child's step function, advancing at each step's time as the clock passes it, and re-extracting the step function whenever child changes - the Go counterpart of Incremental's Clock.incremental_step_function.
func Join ¶
Join creates a node whose value tracks input's current value, unwrapping one level of dynamism - the Go counterpart of Incremental's join. Where Bind rebuilds its right-hand side by running a function whenever the left-hand side changes, Join's right-hand side is simply whatever node input currently points to: input.Value() is itself a node, and Join's result mirrors that node's value, updating whenever input points somewhere new.
func Map ¶
Map creates a node whose value is f applied to input's current value - the Go counterpart of Incremental's map. cutoff decides whether a recompute that produces an unchanged-per-cutoff value should stop propagating to the result's parents.
func Map2 ¶
func Map2[A1, A2, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], f func(A1, A2) R, opts ...NodeOpt[R]) core.ValueNode[R]
Map2 creates a node whose value is f applied to the current values of in1, in2 - the Go counterpart of Incremental's map2.
func Map3 ¶
func Map3[A1, A2, A3, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], f func(A1, A2, A3) R, opts ...NodeOpt[R]) core.ValueNode[R]
Map3 creates a node whose value is f applied to the current values of in1, in2, in3 - the Go counterpart of Incremental's map3.
func Map4 ¶
func Map4[A1, A2, A3, A4, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], in4 core.ValueNode[A4], f func(A1, A2, A3, A4) R, opts ...NodeOpt[R]) core.ValueNode[R]
Map4 creates a node whose value is f applied to the current values of in1, in2, in3, in4 - the Go counterpart of Incremental's map4.
func Map5 ¶
func Map5[A1, A2, A3, A4, A5, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], in4 core.ValueNode[A4], in5 core.ValueNode[A5], f func(A1, A2, A3, A4, A5) R, opts ...NodeOpt[R]) core.ValueNode[R]
Map5 creates a node whose value is f applied to the current values of in1, in2, in3, in4, in5 - the Go counterpart of Incremental's map5.
func Map6 ¶
func Map6[A1, A2, A3, A4, A5, A6, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], in4 core.ValueNode[A4], in5 core.ValueNode[A5], in6 core.ValueNode[A6], f func(A1, A2, A3, A4, A5, A6) R, opts ...NodeOpt[R]) core.ValueNode[R]
Map6 creates a node whose value is f applied to the current values of in1, in2, in3, in4, in5, in6 - the Go counterpart of Incremental's map6.
func Map7 ¶
func Map7[A1, A2, A3, A4, A5, A6, A7, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], in4 core.ValueNode[A4], in5 core.ValueNode[A5], in6 core.ValueNode[A6], in7 core.ValueNode[A7], f func(A1, A2, A3, A4, A5, A6, A7) R, opts ...NodeOpt[R]) core.ValueNode[R]
Map7 creates a node whose value is f applied to the current values of in1, in2, in3, in4, in5, in6, in7 - the Go counterpart of Incremental's map7.
func Map8 ¶
func Map8[A1, A2, A3, A4, A5, A6, A7, A8, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], in4 core.ValueNode[A4], in5 core.ValueNode[A5], in6 core.ValueNode[A6], in7 core.ValueNode[A7], in8 core.ValueNode[A8], f func(A1, A2, A3, A4, A5, A6, A7, A8) R, opts ...NodeOpt[R]) core.ValueNode[R]
Map8 creates a node whose value is f applied to the current values of in1, in2, in3, in4, in5, in6, in7, in8 - the Go counterpart of Incremental's map8.
func Map9 ¶
func Map9[A1, A2, A3, A4, A5, A6, A7, A8, A9, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], in4 core.ValueNode[A4], in5 core.ValueNode[A5], in6 core.ValueNode[A6], in7 core.ValueNode[A7], in8 core.ValueNode[A8], in9 core.ValueNode[A9], f func(A1, A2, A3, A4, A5, A6, A7, A8, A9) R, opts ...NodeOpt[R]) core.ValueNode[R]
Map9 creates a node whose value is f applied to the current values of in1, in2, in3, in4, in5, in6, in7, in8, in9 - the Go counterpart of Incremental's map9.
func Map10 ¶
func Map10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], in4 core.ValueNode[A4], in5 core.ValueNode[A5], in6 core.ValueNode[A6], in7 core.ValueNode[A7], in8 core.ValueNode[A8], in9 core.ValueNode[A9], in10 core.ValueNode[A10], f func(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10) R, opts ...NodeOpt[R]) core.ValueNode[R]
Map10 creates a node whose value is f applied to the current values of in1, in2, in3, in4, in5, in6, in7, in8, in9, in10 - the Go counterpart of Incremental's map10.
func Map11 ¶
func Map11[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], in4 core.ValueNode[A4], in5 core.ValueNode[A5], in6 core.ValueNode[A6], in7 core.ValueNode[A7], in8 core.ValueNode[A8], in9 core.ValueNode[A9], in10 core.ValueNode[A10], in11 core.ValueNode[A11], f func(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11) R, opts ...NodeOpt[R]) core.ValueNode[R]
Map11 creates a node whose value is f applied to the current values of in1, in2, in3, in4, in5, in6, in7, in8, in9, in10, in11 - the Go counterpart of Incremental's map11.
func Map12 ¶
func Map12[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], in4 core.ValueNode[A4], in5 core.ValueNode[A5], in6 core.ValueNode[A6], in7 core.ValueNode[A7], in8 core.ValueNode[A8], in9 core.ValueNode[A9], in10 core.ValueNode[A10], in11 core.ValueNode[A11], in12 core.ValueNode[A12], f func(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12) R, opts ...NodeOpt[R]) core.ValueNode[R]
Map12 creates a node whose value is f applied to the current values of in1, in2, in3, in4, in5, in6, in7, in8, in9, in10, in11, in12 - the Go counterpart of Incremental's map12.
func Map13 ¶
func Map13[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], in4 core.ValueNode[A4], in5 core.ValueNode[A5], in6 core.ValueNode[A6], in7 core.ValueNode[A7], in8 core.ValueNode[A8], in9 core.ValueNode[A9], in10 core.ValueNode[A10], in11 core.ValueNode[A11], in12 core.ValueNode[A12], in13 core.ValueNode[A13], f func(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13) R, opts ...NodeOpt[R]) core.ValueNode[R]
Map13 creates a node whose value is f applied to the current values of in1, in2, in3, in4, in5, in6, in7, in8, in9, in10, in11, in12, in13 - the Go counterpart of Incremental's map13.
func Map14 ¶
func Map14[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], in4 core.ValueNode[A4], in5 core.ValueNode[A5], in6 core.ValueNode[A6], in7 core.ValueNode[A7], in8 core.ValueNode[A8], in9 core.ValueNode[A9], in10 core.ValueNode[A10], in11 core.ValueNode[A11], in12 core.ValueNode[A12], in13 core.ValueNode[A13], in14 core.ValueNode[A14], f func(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14) R, opts ...NodeOpt[R]) core.ValueNode[R]
Map14 creates a node whose value is f applied to the current values of in1, in2, in3, in4, in5, in6, in7, in8, in9, in10, in11, in12, in13, in14 - the Go counterpart of Incremental's map14.
func Map15 ¶
func Map15[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, R any](in1 core.ValueNode[A1], in2 core.ValueNode[A2], in3 core.ValueNode[A3], in4 core.ValueNode[A4], in5 core.ValueNode[A5], in6 core.ValueNode[A6], in7 core.ValueNode[A7], in8 core.ValueNode[A8], in9 core.ValueNode[A9], in10 core.ValueNode[A10], in11 core.ValueNode[A11], in12 core.ValueNode[A12], in13 core.ValueNode[A13], in14 core.ValueNode[A14], in15 core.ValueNode[A15], f func(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15) R, opts ...NodeOpt[R]) core.ValueNode[R]
Map15 creates a node whose value is f applied to the current values of in1, in2, in3, in4, in5, in6, in7, in8, in9, in10, in11, in12, in13, in14, in15 - the Go counterpart of Incremental's map15.
func ReduceBalanced ¶
func ReduceBalanced[A, Acc any](children []core.ValueNode[A], f func(A) Acc, reduce func(Acc, Acc) Acc, opts ...NodeOpt[Acc]) (result core.ValueNode[Acc], ok bool)
ReduceBalanced creates a node whose value combines f applied to every child's value via a balanced binary reduction tree - the Go counterpart of Incremental's reduce_balanced. f transforms each leaf; reduce combines two subtree results. Unlike ArrayFold, a change to one child only dirties the O(log n) nodes on its path to the root rather than the whole fold, at the cost of requiring reduce to be associative. ok is false (and the returned node nil) when children is empty, mirroring the OCaml original's `t option`.
func RemoveDependency ¶
func RemoveDependency[T, U any](n *ExpertNode[T], dep *Dependency[U])
RemoveDependency detaches dep from n and forces n stale so it recomputes without it - the Go counterpart of Incremental's Expert.Node.remove_dependency.
func SaveDot ¶
SaveDot renders the graph reachable from roots (by following child/recompute-dependency edges) as Graphviz "dot" source - the Go counterpart of Incremental's Node_to_dot.save_dot.
emitBindEdges is accepted for API parity with the OCaml original, which additionally draws a dashed edge from a bind's dynamically-created right-hand-side nodes back to the bind. This port's Bind (see bind.go) does not retain that set - only the node f last returned is tracked - so there is nothing to draw and this parameter is currently a no-op.
func Snapshot ¶
func Snapshot[T any](clock *Clock, valueAt core.ValueNode[T], at time.Time, before T) (core.ValueNode[T], error)
Snapshot creates a node that holds "before" until the clock reaches "at", at which point it takes on valueAt's value at that moment and keeps it forever - the Go counterpart of Incremental's Clock.snapshot. It errors if "at" is strictly before the clock's current time; if "at" already equals the current time, it degenerates to Freeze(valueAt, always).
func StepFunction ¶
StepFunction creates a node whose value starts at init and follows steps as the clock advances - the Go counterpart of Incremental's Clock.step_function. It is a convenience over IncrementalStepFunction for a step function fixed at creation.
func Sum ¶
func Sum[T any](inputs []core.ValueNode[T], zero T, add func(T, T) T, sub func(T, T) T, fullComputeEveryNChanges int) core.ValueNode[T]
Sum creates a node folding add over the current values of inputs, starting from zero, updating incrementally via sub (add's inverse) on each change - the Go counterpart of Incremental's sum. fullComputeEveryNChanges <= 0 means "never force a periodic full recompute beyond the first"; pass a positive value to bound floating-point drift.
func UnorderedArrayFold ¶
func UnorderedArrayFold[A, Acc any]( children []core.ValueNode[A], init Acc, f func(Acc, A) Acc, update UnorderedArrayFoldUpdate[A, Acc], fullComputeEveryNChanges int, opts ...NodeOpt[Acc], ) core.ValueNode[Acc]
UnorderedArrayFold creates a node that folds f over the current values of children, order unspecified - the Go counterpart of Incremental's unordered_array_fold. Instead of a full recompute on every change, it applies update incrementally to just the children that changed since the last recompute, falling back to a full recompute every fullComputeEveryNChanges incremental updates (and on the first compute) to bound floating-point/accumulated error. fullComputeEveryNChanges <= 0 means "never force a periodic full recompute beyond the first". If children is empty, the result is a Const holding init.
Types ¶
type BeforeOrAfter ¶
type BeforeOrAfter int
BeforeOrAfter is the value of an At/After incremental: Before until the clock reaches the target time, After from then on - the Go counterpart of Incremental's Before_or_after.t.
const ( Before BeforeOrAfter = iota After )
func (BeforeOrAfter) String ¶
func (b BeforeOrAfter) String() string
String renders b as "Before" or "After".
type Clock ¶
type Clock struct {
// contains filtered or unexported fields
}
Clock drives every time-based incremental (At, AtIntervals, Snapshot, StepFunction/IncrementalStepFunction): a logical clock that only moves when AdvanceClock/AdvanceClockBy is called, so stabilization stays deterministic and test-controllable instead of racing wall-clock time. It is the Go counterpart of Incremental's Clock.
func (*Clock) AdvanceClock ¶
AdvanceClock moves the clock forward to "to" and fires every alarm scheduled at or before "to", in ascending time order. Firing an alarm may itself schedule another one still at or before "to" (e.g. AtIntervals catching up several elapsed intervals in one big jump); AdvanceClock keeps draining until no pending alarm is due anymore, mirroring Incremental's advance_clock followed by fire_past_alarms. It is a no-op if "to" is not after the clock's current time. Like Incremental's advance_clock, it refuses to run while a stabilization round is in flight (ErrStabilizing) unless called reentrantly from an on-update handler.
func (*Clock) AdvanceClockBy ¶
AdvanceClockBy moves the clock forward by span; see AdvanceClock.
func (*Clock) After ¶
After creates a node whose value is Before until span has elapsed on the clock, and After from then on - the Go counterpart of Incremental's Clock.after.
func (*Clock) At ¶
At creates a node whose value is Before until the clock reaches "at", and After from then on - the Go counterpart of Incremental's Clock.at. If "at" is already at or before the clock's current time, the result is a plain Const(After).
func (*Clock) AtIntervals ¶
AtIntervals creates a node whose value changes, without ever cutting off, every interval starting from the clock's current time at creation - the Go counterpart of Incremental's Clock.at_intervals. It panics if interval is not positive.
type Cutoff ¶
type Cutoff[T any] struct { // contains filtered or unexported fields }
Cutoff decides whether propagation of changes should stop at a node, based on the node's old and new value.
func CompareCutoff ¶
CompareCutoff cuts off propagation when compare returns 0.
func EqualCutoff ¶
EqualCutoff cuts off propagation when equal returns true.
func NeverCutoff ¶
NeverCutoff never cuts off propagation; this is also the zero value of Cutoff.
func PhysEqualCutoff ¶
func PhysEqualCutoff[T comparable]() Cutoff[T]
PhysEqualCutoff cuts off propagation when the old and new value compare == equal. This mirrors Incremental's default cutoff (physical equality in OCaml).
func (Cutoff[T]) ShouldCutoff ¶
ShouldCutoff reports whether propagation should stop given a node's old and new value.
type Dependency ¶
type Dependency[T any] struct { // contains filtered or unexported fields }
Dependency is a dynamically-managed edge from an ExpertNode to one of its children - the Go counterpart of Incremental's Expert.Dependency. OnChange, if non-nil, fires with the child's current value both immediately upon being attached (via AddDependency, if the child already has a value) and every time the child's value subsequently changes while attached.
func NewDependency ¶
func NewDependency[T any](child core.ValueNode[T], onChange func(T)) *Dependency[T]
NewDependency creates a Dependency watching child - the Go counterpart of Incremental's Expert.Dependency.create.
func (*Dependency[T]) Value ¶
func (d *Dependency[T]) Value() T
Value returns the dependency's child's current value. It is meant to be called only from within the owning ExpertNode's compute function or the dependency's own OnChange callback, where the child is guaranteed to already have a value.
type DotUserInfo ¶
type DotUserInfo struct {
// contains filtered or unexported fields
}
DotUserInfo is user-controlled metadata optionally attached to a node, used only by SaveDot for debugging/inspection - the Go counterpart of Incremental's Dot_user_info. Core never reads it. The zero value is not valid; construct one with NewDotUserInfo.
func Append ¶
func Append(a, b *DotUserInfo) *DotUserInfo
Append combines a and b into a new DotUserInfo: their label rows form a set union, and their attributes are merged with b's values winning on key collision - the Go counterpart of Incremental's Dot_user_info.append.
func NewDotUserInfo ¶
func NewDotUserInfo(label []string, attributes map[string]string) *DotUserInfo
NewDotUserInfo creates a DotUserInfo with one label row (rendered as a graphviz record whose columns are label's entries) and a set of dot attributes.
func (*DotUserInfo) String ¶
func (info *DotUserInfo) String(name string) string
String renders info as a graphviz node statement named name, using an "Mrecord" shape - the Go counterpart of Incremental's Dot_user_info.to_string.
type ExpertNode ¶
ExpertNode is a node whose set of children (dependencies) and value can both be updated incrementally by user code, rather than fixed at construction - the Go counterpart of Incremental's Expert/Expert1. f computes the node's value from whatever dependencies are currently attached (typically by reading state that each dependency's OnChange callback keeps up to date, rather than reading the dependencies directly); AddDependency and RemoveDependency may be called at any time, including from within f itself.
func NewExpertNode ¶
func NewExpertNode[T any](f func() T, onObservabilityChange func(isNowObservable bool)) *ExpertNode[T]
NewExpertNode creates an ExpertNode with no dependencies yet. onObservabilityChange, if non-nil, is called by SetObservable.
func (*ExpertNode[T]) MakeStale ¶
func (n *ExpertNode[T]) MakeStale()
MakeStale forces n to recompute on the next stabilization even though none of its dependencies changed - the Go counterpart of Incremental's Expert.Node.make_stale.
func (*ExpertNode[T]) OnChildChanged ¶
func (n *ExpertNode[T]) OnChildChanged(child core.AnyNode)
OnChildChanged implements core.ChildValueObserver: it re-fires the OnChange callback of every attached dependency on child, unless a full re-fire is already pending (see SetObservable) - the Go counterpart of Incremental's Expert.run_edge_callback, called from state.ml's maybe_change_value for every changed child.
func (*ExpertNode[T]) Recompute ¶
func (n *ExpertNode[T]) Recompute(now core.StabilizationNum, _ core.RoundCtx)
Recompute implements core.Recomputable. If any attached dependency's child is invalid, n invalidates itself instead of computing - the Go counterpart of Incremental's Expert.before_main_computation returning `Invalid. Otherwise, if a full re-fire is pending, every dependency's OnChange fires with its current value before f runs - the Go counterpart of before_main_computation's `Ok branch running every will_fire_all_callbacks callback - and then f computes n's new value.
func (*ExpertNode[T]) SetObservable ¶
func (n *ExpertNode[T]) SetObservable(isNowObservable bool)
SetObservable notifies n that its observability changed, i.e. whether it currently has a necessary path to some observer - the Go counterpart of Incremental's Expert.observability_change. This engine does not call it automatically; it is exposed for callers that track their own observability. Becoming unobservable arranges for every currently-attached dependency's OnChange to be re-fired in full the next time n recomputes, so callers don't miss changes that happened while n wasn't necessary.
func (*ExpertNode[T]) Watch ¶
func (n *ExpertNode[T]) Watch() core.ValueNode[T]
Watch returns n as a core.ValueNode, for use as another node's input - the Go counterpart of Incremental's Expert.Node.watch.
type Future ¶
Future represents the eventual completion of an asynchronous Stabilize call. See engine.Future: Stabilize never blocks its caller, and Future.Wait is how a caller opts into waiting for a stabilization round to finish.
type Kind ¶
type Kind int
Kind identifies which combinator produced a node. It exists for introspection and debug/dot output; unlike the OCaml GADT it carries no data itself, since node-specific state lives on the node.
const ( KindInvalid Kind = iota KindUninitialized KindConst KindVar KindMap KindMap2 KindMap3 KindMap4 KindMap5 KindMap6 KindMap7 KindMap8 KindMap9 KindMap10 KindMap11 KindMap12 KindMap13 KindMap14 KindMap15 KindBindLHSChange KindBindMain KindJoinLHSChange KindJoinMain KindIfTestChange KindIfThenElse KindFreeze KindArrayFold KindUnorderedArrayFold KindAt KindAtIntervals KindSnapshot KindStepFunction KindExpert KindMapVar KindMapFilter KindMapValues KindMapGroupAggregate KindMapGroupReduce KindMapGroupMinBy KindMapGroupMaxBy KindMapJoin KindMapLeftJoin KindMapLookupJoin )
type MapDiff ¶
type MapDiff[K comparable, V any] struct { Key K Old V New V HasOld bool HasNew bool }
MapDiff is one key's change within a stabilization round. HasOld/HasNew distinguish insert (false/true), update (true/true), delete (true/false).
type MapNode ¶
type MapNode[K comparable, V any] interface { core.ValueNode[uint64] // engine plumbing; the value is a version counter Len() int Get(k K) (V, bool) // ForEach calls fn for every entry until fn returns false. Iteration // order is unspecified. ForEach(fn func(K, V) bool) // contains filtered or unexported methods }
MapNode is the collection counterpart of a ValueNode: its contents are a map, and what propagates between rounds is the per-key diff list - O(keys changed), never O(map). Reads (Len, Get, ForEach) are safe from any goroutine; like Observer.Value they observe the last completed round.
func MapFilter ¶
func MapFilter[K comparable, V any](src MapNode[K, V], pred func(K, V) bool) MapNode[K, V]
MapFilter creates a collection node holding the entries of src whose (key, value) satisfy pred.
func MapGroupAggregate ¶
func MapGroupAggregate[K comparable, V any, G comparable, A any]( src MapNode[K, V], group func(K, V) G, init A, add func(A, V) A, sub func(A, V) A, ) MapNode[G, A]
MapGroupAggregate creates a collection node keyed by group, holding the aggregate of src's entries in that group. add folds a value in; sub must be its inverse (sum, count, ...) - that inverse is what makes each source change O(1) instead of O(group). Groups with no members are removed. For aggregates with no inverse use MapGroupReduce, or MapGroupMinBy/MapGroupMaxBy for extremes over ordered values.
func MapGroupMaxBy ¶
func MapGroupMaxBy[K comparable, V any, G comparable, W cmp.Ordered]( src MapNode[K, V], group func(K, V) G, by func(K, V) W, ) MapNode[G, W]
MapGroupMaxBy is MapGroupMinBy for the largest by(key, value) per group.
func MapGroupMinBy ¶
func MapGroupMinBy[K comparable, V any, G comparable, W cmp.Ordered]( src MapNode[K, V], group func(K, V) G, by func(K, V) W, ) MapNode[G, W]
MapGroupMinBy creates a collection node keyed by group, holding the smallest by(key, value) among that group's entries in src. Groups with no members are removed.
Unlike MapGroupReduce, removals stay cheap: each group keeps a multiset of its projected values, so dropping a value that is not the current extreme is O(1), and dropping the extreme itself costs one pass over the group's distinct values. by and group must be pure - the node recomputes them on the old value to undo an entry, rather than storing a copy per key.
func MapGroupReduce ¶
func MapGroupReduce[K comparable, V any, G comparable, A any]( src MapNode[K, V], group func(K, V) G, init A, combine func(A, V) A, ) MapNode[G, A]
MapGroupReduce is MapGroupAggregate for folds that have no inverse (min, max, set union, "any member satisfies p", ...). combine must be associative and commutative: a group's members are folded in unspecified order.
Insertions stay O(1) - combine folds the new value into the group's running aggregate. Removals and in-place value changes cannot be undone without an inverse, so a group that loses or rewrites a member is refolded from its members: O(group size), once per round per touched group however many of its members changed. Prefer MapGroupAggregate when an inverse exists, and MapGroupMinBy/MapGroupMaxBy for extremes over ordered values - both keep removals cheap.
func MapJoin ¶
func MapJoin[K comparable, L, R, O any]( left MapNode[K, L], right MapNode[K, R], combine func(K, L, R) O, ) MapNode[K, O]
MapJoin creates a collection node holding combine(key, l, r) for every key present in both left and right - an incremental inner equi-join on the key.
Cost is O(keys changed on either side), not O(left + right): a round only revisits the keys whose left or right entry moved, reading the other side's settled contents for each. Keys that leave either side are removed from the output.
func MapLeftJoin ¶
func MapLeftJoin[K comparable, L, R, O any]( left MapNode[K, L], right MapNode[K, R], combine func(k K, l L, r R, hasRight bool) O, ) MapNode[K, O]
MapLeftJoin keeps every key of left, passing combine the right entry and whether it was present - the incremental left outer equi-join. When a key arrives on or leaves the right side, only that key's output is recombined.
func MapLookupJoin ¶
func MapLookupJoin[K comparable, V any, FK comparable, T, O any]( src MapNode[K, V], table MapNode[FK, T], on func(K, V) FK, combine func(k K, v V, t T, hasRow bool) O, ) MapNode[K, O]
MapLookupJoin enriches every entry of src with the table entry its foreign key points at - the incremental form of a stream/table join, where one table entry may serve many src entries. combine receives the table value and whether it was present, so a src entry with no matching row still produces output (drop those in a downstream MapFilter if an inner join is wanted).
The node keeps a reverse index from table key to the src keys referencing it, so a table change only revisits rows that actually reference it. Cost per round is O(src keys changed + src rows referencing changed table keys): a table entry with many referrers costs proportionally, which is inherent to one-to-many, not an artefact. Output is keyed by src's key space.
on must be pure - the node recomputes it against the old value to un-index an entry, rather than storing a copy of every key's foreign key.
type MapObserver ¶
type MapObserver[K comparable, V any] struct { // contains filtered or unexported fields }
MapObserver keeps a MapNode necessary and provides snapshot and per-round diff access - the collection counterpart of Observer.
func ObserveMap ¶
func ObserveMap[K comparable, V any](state *State, node MapNode[K, V]) *MapObserver[K, V]
ObserveMap roots node so stabilization keeps it up to date.
func (*MapObserver[K, V]) Get ¶
func (o *MapObserver[K, V]) Get(k K) (V, bool)
func (*MapObserver[K, V]) Len ¶
func (o *MapObserver[K, V]) Len() int
Len and Get proxy the observed node.
func (*MapObserver[K, V]) Node ¶
func (o *MapObserver[K, V]) Node() MapNode[K, V]
func (*MapObserver[K, V]) OnDiffs ¶
func (o *MapObserver[K, V]) OnDiffs(fn func([]MapDiff[K, V]))
OnDiffs registers fn to run at the end of every round in which the observed map changed, with that round's diffs. fn runs on the scheduler goroutine and must not retain the slice - it is reused next round.
func (*MapObserver[K, V]) Snapshot ¶
func (o *MapObserver[K, V]) Snapshot() map[K]V
Snapshot returns a copy of the observed map's current contents.
func (*MapObserver[K, V]) Unobserve ¶
func (o *MapObserver[K, V]) Unobserve()
Unobserve releases the root and stops diff delivery.
type MapVar ¶
type MapVar[K comparable, V any] struct { // contains filtered or unexported fields }
MapVar is the collection leaf: a map whose entries are set directly. Like Var, mutations never block and take effect at the next stabilization.
func NewMapVar ¶
func NewMapVar[K comparable, V any](state *State) *MapVar[K, V]
NewMapVar creates an empty MapVar in state's graph.
func (*MapVar[K, V]) Delete ¶
func (m *MapVar[K, V]) Delete(k K)
Delete schedules k's removal as of the next stabilization.
func (*MapVar[K, V]) Set ¶
func (m *MapVar[K, V]) Set(k K, v V)
Set schedules k to hold v as of the next stabilization.
type NodeOpt ¶
type NodeOpt[T any] func(*nodeOptions[T])
NodeOpt configures a combinator's result node. Combinators take options variadically so the common case stays terse - Map(x, f) - while cutoffs remain available: Map(x, f, WithCutoff(EqualCutoff(eq))).
func WithCutoff ¶
WithCutoff sets the result node's cutoff: recomputes whose new value the cutoff reports as unchanged do not propagate to parents. The default is NeverCutoff (every recompute counts as a change).
type NodeUpdate ¶
type NodeUpdate[T any] = core.NodeUpdate[T]
NodeUpdate describes a single on-update-handler notification, delivered at the end of a stabilization round. OldValue is only meaningful when Kind is Changed; NewValue is only meaningful when Kind is Necessary or Changed.
type NodeUpdateKind ¶
type NodeUpdateKind = core.NodeUpdateKind
NodeUpdateKind classifies what happened to an observed node during a stabilization round.
type Observer ¶
type Observer[T any] struct { // contains filtered or unexported fields }
Observer is a root of the incremental DAG: for as long as it is linked, the node it observes (and everything that node depends on) is necessary, so Stabilize keeps it up to date. Create one with Observe; call Unobserve when done so the node can become unnecessary again.
func Observe ¶
Observe creates an Observer rooted at node. node becomes (and stays, until Unobserve) necessary.
func (*Observer[T]) OnUpdate ¶
func (o *Observer[T]) OnUpdate(fn func(NodeUpdate[T])) error
OnUpdate registers fn to run, in registration order, at the end of every stabilization round for as long as the observer stays linked; a newly registered fn still gets a Necessary delivery for the current value at the next round, even if nothing changes. fn must not call Set or Stabilize synchronously: the scheduler goroutine calls fn while completing a round, so doing so would deadlock, except Set on a Var, which is safe to call reentrantly from an on-update handler.
type State ¶
State owns an incremental graph's scheduling: registered roots, the worker pool, and the single goroutine that serializes graph mutations. Create one with NewState; call Close when done with it. See the package doc for the concurrency model Stabilize and StabilizeAndWait give you.
func NewState ¶
NewState creates a State with a worker pool sized to workers, defaulting to runtime.GOMAXPROCS(0) when workers <= 0; pass 1 to rule out concurrent recompute. Rounds only reach the pool when their measured per-node cost justifies it, so a graph of cheap nodes stays serial without being asked to. The returned State owns a goroutine; call Close when done with it.
type StepFunctionValue ¶
StepFunctionValue is a function from time.Time to T with a finite number of steps, in nondecreasing time order - the Go counterpart of Incremental's Step_function.t.
func NewStepFunctionValue ¶
func NewStepFunctionValue[T any](init T, steps []Step[T]) StepFunctionValue[T]
NewStepFunctionValue creates a StepFunctionValue with value init before the first step, stepping to each step's Value at its At time in turn - the Go counterpart of Incremental's Step_function.create_exn. It panics if steps isn't in nondecreasing time order.
func (StepFunctionValue[T]) ValueAt ¶
func (sf StepFunctionValue[T]) ValueAt(at time.Time) T
ValueAt returns the step function's value at the given time - the Go counterpart of Incremental's Step_function.value.
type UnorderedArrayFoldUpdate ¶
type UnorderedArrayFoldUpdate[A, Acc any] struct { // FInverse removes a child's prior contribution, then f re-adds its new one: // update(acc, old, new) = f(FInverse(acc, old), new). Set this when f has a natural // inverse (e.g. sum/add and subtract). FInverse func(acc Acc, oldValue A) Acc // Update computes the new accumulator directly from the old and new child value, and // takes precedence over FInverse when both are set. Update func(acc Acc, oldValue, newValue A) Acc }
UnorderedArrayFoldUpdate tells UnorderedArrayFold how to fold a single child's change into the accumulator without a full recompute over every child - the Go counterpart of Incremental's Unordered_array_fold.Update.
type ValueNode ¶
ValueNode is the read side of a node in the DAG: anything that produces a value of type T during stabilization. All combinators accept and return ValueNodes; consumers should use this alias rather than reaching into internal packages.
type Var ¶
type Var[T any] struct { // contains filtered or unexported fields }
Var is a leaf of the incremental DAG: its value is supplied directly by calling Set, rather than derived from other nodes.
func (*Var[T]) Set ¶
func (v *Var[T]) Set(value T)
Set updates v's value. If called while a stabilization round is in flight, the change is applied once that round completes, so it only becomes visible to the graph starting with the next round - Set never blocks and never races an in-flight round's recompute.
Source Files
¶
- all.go
- array_fold.go
- at.go
- at_intervals.go
- before_or_after.go
- bind.go
- both.go
- clock.go
- const.go
- cutoff.go
- dot_user_info.go
- expert.go
- freeze.go
- future.go
- if_then_else.go
- incr_map.go
- incr_map_extreme.go
- incr_map_join.go
- incr_map_lookup_join.go
- incr_map_ops.go
- incr_map_reduce.go
- incremental.go
- join.go
- kind.go
- map.go
- node_to_dot.go
- observer.go
- on_update_handler.go
- options.go
- reduce_balanced.go
- snapshot.go
- step_function.go
- unordered_array_fold.go
- var.go
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
core
Package core holds internal data structures shared by the incremental engine that are not part of the public API.
|
Package core holds internal data structures shared by the incremental engine that are not part of the public API. |
|
engine
Package engine implements the non-blocking concurrent stabilization engine: a wavefront dataflow scheduler that replaces Incremental's single-threaded, height-ordered recompute heap.
|
Package engine implements the non-blocking concurrent stabilization engine: a wavefront dataflow scheduler that replaces Incremental's single-threaded, height-ordered recompute heap. |