Salsa-inspired incremental recomputation library with automatic dependency tracking, backdating, and durability-based verification skipping
Dependencies
let rt = Runtime()
// Create inputs
let x = rt.input(10, label="x")
let y = rt.input(20, label="y")
// Combine inputs and chain further derived stages
let sum = x.derived2(y, (a, b) => a + b, label="sum")
let doubled = sum.map(v => v * 2, label="doubled")
// Outside the graph, read with `read_or_abort()` or `read()`.
// (`.get()` is only legal inside another derived computation —
// use `Derived(rt, () => ...)` when a stage reads several cells.)
inspect(sum.read_or_abort(), content="30")
inspect(doubled.read_or_abort(), content="60")
// Update an input — downstream derived values recompute on the next read
x.set(5)
inspect(doubled.read_or_abort(), content="50")Note on the example above: It is nocheck because the snippet omits imports and a test wrapper. The same construction is checked in docs/target_api_examples.mbt.md and exercised end-to-end by tests/quickstart_test.mbt — if you edit the example, update those in lockstep.
{
"import": ["dowdiness/incr"]
}| Type | What it is for |
|---|---|
| DerivedMap[K, V] | One cached derived value per key (e.g. type_of(function_id)), created lazily on first read |
| InputField[T] | One input cell per field of a struct, so changing one field doesn't disturb readers of the others |
| EagerDerived[T] / Effect | Values/side effects that update immediately when inputs change (push style) — for UI-facing state |
| ReachableDerived[T] | A lazy derived value that must stay alive across garbage collection while something downstream watches it |
| Accumulator[T] | A side channel for log-like data (diagnostics, traces) emitted during computation |
| Relation / MapRelation | Datalog-style facts and rules, computed to a fixed point |
| Need | Use |
|---|---|
| Default cached computation, especially if it may not be read after every input write | Derived |
| One memoized value per semantic key, created lazily | DerivedMap |
| Field-level invalidation inside a larger object | InputField |
| UI-facing value that should stay eagerly current after input writes | EagerDerived |
| Side effect that should run eagerly when dependencies change | Effect |
| Lazy derived value that must stay alive through downstream push subscribers or long-lived watches | ReachableDerived |
| Relational/fixpoint computation | Relation / MapRelation |
moon check # Type-check the workspace
moon build # Build the workspace
moon test # Run all workspace tests
moon bench # Run benchmarks (always pass --release for representative numbers)match memo.get_result() {
Ok(value) => println("Got: " + value.to_string())
Err(err) => {
println("Cycle at cell " + err.cell().to_string())
println(err.format_path())
}
}let rt = Runtime()
let count = Input(rt, 0)
count.set(5)
inspect(count.get(), content="5")let rt = Runtime()
let field = InputField(rt, 0, label="counter")
field.set(5)
inspect(field.get(), content="5")let rt = Runtime()
let x = Input(rt, 10)
let doubled = Memo(rt, () => x.get() * 2)let scope = Scope::new(rt)
let local = scope.input(42)
let derived = scope.derived(fn() { local.get() * 2 })
// Component unmounts — one cleanup call
scope.dispose()pub(open) trait Freshness {
fn is_fresh(Self) -> Bool
}impl Freshness for InputField[T]impl Freshness for ReachableDerived[T]struct MyDb {
rt : @incr.Runtime
}
impl @incr.RuntimeContext for MyDb with fn runtime(self) {
self.rt
}let DURABILITY_COUNT : Intlet MAX_CYCLE_DISPLAY_STEPS : Intlet scope = Scope::new(rt)
let owner = MyTracked::new(rt)
add_input_fields(scope, owner)
scope.dispose() // disposes all owned cellsfn[Ctx : RuntimeContext, T : Eq] create_derived(ctx : Ctx, f : () -> T raise Failure, label? : String) -> Derived[T]fn[Ctx : RuntimeContext, K : Hash + Eq, V] create_derived_map(ctx : Ctx, f : (K) -> V raise Failure, label? : String) -> DerivedMap[K, V]fn[Ctx : RuntimeContext, T : Eq] create_eager_derived(ctx : Ctx, compute : () -> T) -> EagerDerived[T]fn[Ctx : RuntimeContext, T] create_input(ctx : Ctx, value : T, durability? : Durability, label? : String) -> Input[T]fn[Ctx : RuntimeContext, T] create_input_field(ctx : Ctx, value : T, durability? : Durability, label? : String) -> InputField[T]fn[Ctx : RuntimeContext, T : Eq] create_reachable_derived(ctx : Ctx, f : () -> T raise Failure, label? : String) -> ReachableDerived[T]Salsa-inspired incremental recomputation library with automatic dependency tracking, backdating, and durability-based verification skipping
Dependencies