README

dowdiness/incr/cells does not have a README file

#
BackdateEq

A backdate-aware equality check for memo outputs.

The default implementation compares changed_at revisions: two values are considered "backdate equal" (i.e., the memo should not advance its changed_at) when both carry the same revision stamp. Override to provide custom logic.

Supertrait

Requires HasChangedAt — the type must expose a changed_at revision.

#
CellId

A unique identifier for a cell (signal or memo) in the runtime.

Cell IDs are monotonically increasing integers allocated by the runtime. They serve as direct array indices for O(1) cell lookup.

Each CellId is scoped to its originating runtime via runtime_id. This prevents cells from different runtimes being incorrectly queried against each other.

#
CycleError

Error type for cycle detection during memo computation.

Returned by get_result() methods when a memo transitively depends on itself. Use cell(), path(), and format_path() to inspect or render the cycle. Labels are snapshotted at construction time so format_path is pure-value — rendering needs no runtime handle and reflects the cell labels at the moment the cycle was detected, even if those cells are later renamed or disposed.

Example

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())
}
}

#
Durability

Classifies how often an input is expected to change.

Durability enables a powerful optimization: when only low-durability inputs change, memos that depend solely on high-durability inputs skip verification entirely.

Levels

  • Low: Frequently changing values (user input, source text)
  • Medium: Moderately stable values
  • High: Rarely changing values (configuration, schemas)

Inherited Durability

Memos inherit the minimum durability of their dependencies. A memo depending on both Low and High durability signals has Low durability.

#
ListenerId

A handle identifying a single runtime-global listener registration.

Returned by the additive listener APIs (Runtime::add_on_change_listener, Runtime::add_derived_event_listener) and passed back to their remove_* counterparts to detach a specific listener.

An id pairs the originating RuntimeId with an allocation number drawn from a single per-runtime monotonic counter shared across both listener registries. The counter makes ids unique across the two registries of one runtime; the RuntimeId makes them unique across runtimes (the bare counter alone would collide — the first listener of two runtimes would both be number 0). Together they guarantee that passing an id to the wrong registry, or to a different runtime, is a no-op rather than an accidental removal of an unrelated hook.

Like RuntimeId, a ListenerId is an introspection/debug identity — compare it for equality or use it as a map key; it is not a stable application key across program runs and carries no ordering or arithmetic meaning.

#
ReadError

Error type for the public read channel.

A read asks for a stored graph snapshot, so the only failures it can report are mechanism failures — intrinsic to the read itself and unrecoverable by any domain reader:

  • Cycle — the cell transitively depends on itself.
  • Disposed — the cell being read has been disposed.

Domain fallibility (a parse error, a validation failure) is not a read error: it belongs in the value as Result[V, E] (see Derived::fallible), where it is cached, change-detected, and replayed like any other value. A retained compute raise Failure is a defect, not a domain error, and still aborts.

Cross-runtime misuse and strict-context misuse are programmer defects and abort rather than surfacing here.

See docs/design/specs/2026-05-28-honest-read-error-ownership.md.

#
Revision

A monotonically increasing revision counter representing the system's logical clock.

Revisions are bumped each time an input signal changes. Every cell tracks two revision timestamps:

  • changed_at: When this cell's value last actually changed
  • verified_at: When this cell was last confirmed up-to-date

A cell is stale when verified_at < current_revision. A cell has changed (relative to an observer) when changed_at > observer.verified_at.

#
InputFieldOwner

pub(open) trait InputFieldOwner {
fn cell_ids(Self) -> Array[
CellId
]
}

Contract for types that expose their runtime-tracked cell ids — typically structs that own InputField fields, but any cell handle qualifies.

The ordering of CellIds returned by cell_ids() must be stable across calls (i.e., always return fields in the same order), and the ids must belong to the runtime of any scope they are registered with.

#
AcceptStatus

pub(all) enum AcceptStatus {
NoAccept
AcceptedChanged
AcceptedUnchanged
RetainedDueToError
} derive(Eq)

Status of a single committed-revision transition through the accept gate. Mirrors the spec's state-machine Status column.

#
AcceptedDerived

pub struct AcceptedDerived[V, E] {
// private fields
}

A success-gated derived value. Construct with AcceptedDerived::AcceptedDerived (owns its candidate compute), AcceptedDerived::from_candidate (wraps an existing candidate Derived), or Scope::accepted_derived — all V : Eq. For candidate values that are not Eq but carry a Revision, use the BackdateEq tier (AcceptedDerived::accepted_memo / Scope::accepted_memo), which gates acceptance by revision identity.

#
AcceptedDerived::AcceptedDerived

fn[V : Eq, E : Eq] AcceptedDerived::AcceptedDerived(rt : Runtime, compute : () -> Result[V, E], label? : String) -> AcceptedDerived[V, E]

Builds an AcceptedDerived that owns its candidate compute. Domain failures are values (Result[V, E]), never raised — the compute is noraise like Derived::fallible.

#
AcceptedDerived::accepted

fn[V, E] AcceptedDerived::accepted(self : AcceptedDerived[V, E]) -> Result[V?,
ReadError
]

The last accepted value, or a read error. The retained accepted value is left untouched by a read error; the read returns the error in its place.

This is an OUTSIDE-graph read: it surfaces the candidate read channel and, when called inside a tracked compute, records a dependency on the current candidate. An in-graph accepted-only consumer must use accepted_get / accepted_get_or_abort instead, which depend on the accepted projection and so re-run only when the accepted value changes — not on current-error churn.

#
AcceptedDerived::accepted_changed_at

fn[V, E] AcceptedDerived::accepted_changed_at(self : AcceptedDerived[V, E]) ->
Revision

Revision at which the accepted value last actually changed. Gated solely by V-equality on the accepted value: current-result churn (changing diagnostics, repeated errors, equal successful recomputations) never advances it. This is an incr graph Revision, not a domain document revision.

#
AcceptedDerived::accepted_get

fn[V, E] AcceptedDerived::accepted_get(self : AcceptedDerived[V, E]) -> Result[V?,
ReadError
]

INSIDE-graph read of the accepted value. Call this from a compute closure (Derived / EagerDerived / Effect): it reads the accepted projection, so it records a dependency on the accepted value, not the candidate. The consumer therefore re-runs only when the accepted value actually changes — never on current-error churn (Err(e1) -> Err(e2) with the accepted value retained backdates the projection). Returns Err only for a read failure of the accepted projection itself; the candidate's read channel is the concern of the outside-graph accepted / accepted_or_abort.

#
AcceptedDerived::accepted_get_or_abort

fn[V, E] AcceptedDerived::accepted_get_or_abort(self : AcceptedDerived[V, E]) -> V?

Strict inside-graph companion to accepted_get; aborts on a read error (cycle / disposed), as in-graph strict reads do. Records a dependency on the accepted projection.

#
AcceptedDerived::accepted_memo

fn[V :
BackdateEq
+
HasChangedAt
, E : Eq] AcceptedDerived::accepted_memo(rt : Runtime, compute : () -> Result[V, E], label? : String) -> AcceptedDerived[V, E]

BackdateEq companion of AcceptedDerived::AcceptedDerived: owns its candidate compute, accepts by revision identity. Requires V : BackdateEq,E : Eq.

#
AcceptedDerived::accepted_or_abort

fn[V, E] AcceptedDerived::accepted_or_abort(self : AcceptedDerived[V, E]) -> V?

Strict accepted read; aborts on a read error.

#
AcceptedDerived::current

fn[V, E] AcceptedDerived::current(self : AcceptedDerived[V, E]) -> Result[Result[V, E],
ReadError
]

The current candidate result for the latest committed revision. The outer Result is the read channel (Cycle / Disposed); the inner Result[V, E] is the domain candidate.

#
AcceptedDerived::current_or_abort

fn[V, E] AcceptedDerived::current_or_abort(self : AcceptedDerived[V, E]) -> Result[V, E]

Strict current read; aborts on a read error.

#
AcceptedDerived::dispose

fn[V, E] AcceptedDerived::dispose(self : AcceptedDerived[V, E]) -> Unit

Disposes this AcceptedDerived. Idempotent. For from_candidate, the external candidate is NOT disposed (the caller owns it).

#
AcceptedDerived::from_candidate

fn[V : Eq, E] AcceptedDerived::from_candidate(candidate : Derived[Result[V, E]], label? : String) -> AcceptedDerived[V, E]

Builds an AcceptedDerived over an existing candidate Derived. The candidate's lifecycle is owned by the CALLER — AcceptedDerived::dispose disposes only the fold, accepted projection, and gc anchor, not the candidate.

#
AcceptedDerived::is_disposed

fn[V, E] AcceptedDerived::is_disposed(self : AcceptedDerived[V, E]) -> Bool

Returns true if this AcceptedDerived has been disposed.

#
AcceptedDerived::snapshot

A coherent snapshot (current + accepted + status), or a read error.

#
AcceptedDerived::snapshot_or_abort

fn[V, E] AcceptedDerived::snapshot_or_abort(self : AcceptedDerived[V, E]) -> AcceptedSnapshot[V, E]

Strict snapshot read; aborts on a read error.

#
AcceptedDerived::watch_accepted

fn[V, E] AcceptedDerived::watch_accepted(self : AcceptedDerived[V, E]) -> Watch[V?]

A persistent outside-graph anchor on the accepted projection. The caller owns the returned Watch and must dispose() it when done. It backdates with the accepted value, so it is woken only when the accepted value actually changes — not on current-error churn.

#
AcceptedSnapshot

pub struct AcceptedSnapshot[V, E] {
current : Result[V, E]
accepted : V?
status : AcceptStatus
} derive(Eq)

A coherent view of one committed revision: the current candidate result, the retained accepted value, and the transition status. current carries the domain Result[V, E] (the read-error channel lives on the accessor return type, not here).

#
Accumulator

pub struct Accumulator[T] {
// private fields
}

A side-channel collector for values pushed by memo compute closures.

Typed buffers (per_memo, prev_push_sets) live on the handle; runtime holds only type-erased closures in SlotMeta. See the design doc docs/superpowers/specs/2026-04-19-accumulator-api-design.md.

#
Accumulator::Accumulator

fn[T : Eq] Accumulator::Accumulator(rt : Runtime, label? : String) -> Accumulator[T]

Creates a new accumulator bound to the given runtime.

Constructs the typed buffer state and the SlotMeta closures that the runtime uses to drive snapshot/finalize/dispose phases.

#
Accumulator::debug

fn[T] Accumulator::debug(self : Accumulator[T]) -> String

Debug representation.

#
Accumulator::dispose

fn[T] Accumulator::dispose(self : Accumulator[T]) -> Unit

Disposes the accumulator. Idempotent. Clears typed buffers and removes reverse-index entries from rt.accumulator_contributions. The slot_id stays allocated — monotonic, never reused.

#
Accumulator::id

Returns the slot's unique ID.

#
Accumulator::is_disposed

fn[T] Accumulator::is_disposed(self : Accumulator[T]) -> Bool

Returns true iff this accumulator has been disposed.

#
Accumulator::label

fn[T] Accumulator::label(self : Accumulator[T]) -> String?

Returns the optional label provided at construction.

#
Accumulator::push

fn[T] Accumulator::push(self : Accumulator[T], value : T) -> Unit raise Failure

Appends a value to this accumulator, keyed by the currently-computing memo's CellId.

Errors

  • fail if called outside a tracked compute context
  • fail if the top tracking frame is not a Memo (MVP restriction)
  • fail if this accumulator has been disposed
  • abort (via check_cross_runtime) if slot belongs to a different runtime

#
CellInfo

Structured metadata about a cell in the dependency graph.

This structure provides a uniform view of both Input and Derived cells. For inputs, the dependencies array will be empty.

Fields

  • label: Optional human-readable name for debugging and introspection
  • id: The unique identifier for this cell
  • changed_at: When this cell's value last actually changed
  • verified_at: When this cell was last confirmed up-to-date
  • durability: How often this cell is expected to change
  • dependencies: Cell IDs this cell depends on (empty for inputs)

#
Derived

pub(all) struct Derived[T] {
// private fields
}

Target-name lazy derived cell facade. Owns its fields directly — no wrapper indirection.

#
Derived::Derived

fn[T : Eq] Derived::Derived(rt : Runtime, compute : () -> T raise Failure, label? : String) -> Derived[T]

Creates a lazy derived value.

#
Derived::accumulated

fn[T, A] Derived::accumulated(self : Derived[T], acc : Accumulator[A]) -> Result[Array[A],
ReadError
] raise Failure

Tracked accumulator read intended for use inside a compute closure. Returns a defensive copy of the values self pushed during its last successful compute, forces verification of self, and stages a synthetic dep on the current frame.

#
Derived::accumulated_or_abort

fn[T, A] Derived::accumulated_or_abort(self : Derived[T], acc : Accumulator[A]) -> Array[A] raise Failure

#
Derived::accumulated_peek

fn[T, A] Derived::accumulated_peek(self : Derived[T], acc : Accumulator[A]) -> Array[A]

Untracked accumulator read. Returns [] when the accumulator or target derived value is disposed; otherwise a defensive copy. Permissive on disposal: returns [] if the accumulator or target memo is disposed. Matches Input::peek semantics.

#
Derived::accumulated_result

fn[T, A] Derived::accumulated_result(self : Derived[T], acc : Accumulator[A]) -> Result[Array[A],
ReadError
] raise Failure

Compatibility alias: Result-style accumulator read on Derived.

#
Derived::changed_at

Revision at which this derived value's content last actually changed. Backdated by structural equality: when a recomputation produces a value equal to the previous one, changed_at is preserved rather than advanced.

#
Derived::clear_on_change

fn[T] Derived::clear_on_change(self : Derived[T]) -> Unit

Removes the on_change callback for this derived value.

#
Derived::dependencies

fn[T] Derived::dependencies(self : Derived[T]) -> Array[
CellId
]

Returns the array of cell IDs that this derived value depends on.

#
Derived::derived_no_backdate

fn[T] Derived::derived_no_backdate(rt : Runtime, compute : () -> T raise Failure, label? : String) -> Derived[T]

Creates a lazy derived value without equality-based backdating. Each recomputation advances the changed-at revision unconditionally, even when the output equals the previous value. Accepts output types that do not implement Eq.

#
Derived::dispose

fn[T] Derived::dispose(self : Derived[T]) -> Unit

Disposes this derived value, freeing associated resources. After disposal, reads abort with Disposed.

#
Derived::expr

#alias(e)
fn[T] Derived::expr(self : Derived[T]) -> Expr[T]

#
Derived::fallible

fn[V : Eq, E : Eq] Derived::fallible(rt : Runtime, compute : () -> Result[V, E], label? : String) -> Derived[Result[V, E]]

Creates a fallible lazy derived value: a recoverable, domain-specific failure is expressed in the value as Result[V, E], never raised. The compute is noraise, so the domain error is forced into the value, where it is cached, change-detected (Eq), and replayed like any other value; reads surface only graph failures (cycles), never an uncatchable abort. A raise Failure from a plain Derived compute is a defect, not a domain error. See docs/design/specs/2026-05-28-honest-read-error-ownership.md.

#
Derived::get

fn[T] Derived::get(self : Derived[T]) -> Result[T,
ReadError
]

Strict graph read. Requires an active tracked context.

#
Derived::get_or_abort

fn[T] Derived::get_or_abort(self : Derived[T]) -> T

Strict graph read that aborts on invalid context or any read error.

#
Derived::id

Returns the unique cell identifier for this derived value. Stable across reads — useful for graph-shape probes (gc anchoring, edge inspection) where a cell needs an identity independent of its value.

#
Derived::is_disposed

fn[T] Derived::is_disposed(self : Derived[T]) -> Bool

Returns true if this derived value has been disposed.

#
Derived::is_fresh

fn[T] Derived::is_fresh(self : Derived[T]) -> Bool

Returns whether this derived value is verified at the current revision.

#
Derived::map

fn[T, U : Eq] Derived::map(self : Derived[T], f : (T) -> U, label? : String) -> Derived[U]

Transforms this derived value into another Eq-backdated derived value on the same runtime.

When recomputation produces a mapped value equal to the previous mapped value, the returned cell preserves its changed_at timestamp so downstream dependents can skip recomputation.

#
Derived::map2

fn[T1, T2, U : Eq] Derived::map2(self : Derived[T1], other : Derived[T2], f : (T1, T2) -> U, label? : String) -> Derived[U]

Combines two derived values into another Eq-backdated derived value.

Aborts if other belongs to a different runtime. When recomputation produces a mapped value equal to the previous mapped value, the returned cell preserves its changed_at timestamp so downstream dependents can skip recomputation.

#
Derived::map2_no_backdate

fn[T1, T2, U] Derived::map2_no_backdate(self : Derived[T1], other : Derived[T2], f : (T1, T2) -> U, label? : String) -> Derived[U]

Combines two derived values on the same runtime.

The mapped value never backdates, so U does not need to implement Eq. Aborts if other belongs to a different runtime.

#
Derived::map3

fn[T1, T2, T3, U : Eq] Derived::map3(self : Derived[T1], second : Derived[T2], third : Derived[T3], f : (T1, T2, T3) -> U, label? : String) -> Derived[U]

Combines three derived values into another Eq-backdated derived value.

Aborts if any input belongs to a different runtime. When recomputation produces a mapped value equal to the previous mapped value, the returned cell preserves its changed_at timestamp so downstream dependents can skip recomputation.

#
Derived::map3_no_backdate

fn[T1, T2, T3, U] Derived::map3_no_backdate(self : Derived[T1], second : Derived[T2], third : Derived[T3], f : (T1, T2, T3) -> U, label? : String) -> Derived[U]

Combines three derived values on the same runtime.

The mapped value never backdates, so U does not need to implement Eq. Aborts if any input belongs to a different runtime.

#
Derived::map_no_backdate

fn[T, U] Derived::map_no_backdate(self : Derived[T], f : (T) -> U, label? : String) -> Derived[U]

Transforms this derived value into another derived value on the same runtime.

The mapped value never backdates, so U does not need to implement Eq.

#
Derived::on_change

fn[T] Derived::on_change(self : Derived[T], f : (T) -> Unit) -> Unit

Registers a callback that fires whenever this derived value's output changes.

#
Derived::read

fn[T] Derived::read(self : Derived[T]) -> Result[T,
ReadError
]

Permissive read. Works outside the graph and records a dependency if tracked.

#
Derived::read_or_abort

fn[T] Derived::read_or_abort(self : Derived[T]) -> T

Permissive read that aborts on any read error.

#
Derived::verified_at

fn[T] Derived::verified_at(self : Derived[T]) ->
Revision

Returns the revision at which this derived value was last verified.

#
Derived::watch

fn[T] Derived::watch(self : Derived[T]) -> Watch[T]

Creates a long-lived outside-graph reader that returns read errors.

Performs one priming read before returning so the target's upstream dependencies are recorded for Runtime::gc(). A priming read error is not escalated; it remains observable through Watch::read().

#
Derived::with_backdate

fn[T :
BackdateEq
+
HasChangedAt
] Derived::with_backdate(rt : Runtime, compute : () -> T raise Failure, label? : String) -> Derived[T]

Creates a lazy derived value using BackdateEq for change detection.

T must implement BackdateEq (and its supertrait HasChangedAt). The backdate check calls BackdateEq::backdate_equal, not structural Eq.

#
DerivedAbortedEvent

pub(all) struct DerivedAbortedEvent {
cell_id :
CellId

elapsed_ns : Int64
started_revision :
Revision

error : Error
}

Event emitted when a derived recompute raises a catchable error.

#
DerivedCompletedEvent

pub(all) struct DerivedCompletedEvent {
cell_id :
CellId

elapsed_ns : Int64
started_revision :
Revision

verified_at :
Revision

changed_at :
Revision

backdated : Bool
}

Event emitted when a derived recompute completes successfully.

#
DerivedEnteringEvent

pub(all) struct DerivedEnteringEvent {
cell_id :
CellId

started_revision :
Revision

}

Event emitted when a derived recompute starts.

#
DerivedEvent

pub(all) enum DerivedEvent {
EnteringCompute(DerivedEnteringEvent)
Completed(DerivedCompletedEvent)
Aborted(DerivedAbortedEvent)
}

Derived recompute lifecycle event delivered by Runtime::on_derived_event.

#
DerivedMap

pub(all) struct DerivedMap[K, V] {
// private fields
}

Keyed derived map facade. Lazily caches per-key Derived entries on first access; scope-owned maps clear entries on dispose and participate in Scope::collect() maintenance.

#
DerivedMap::DerivedMap

fn[K : Hash + Eq, V] DerivedMap::DerivedMap(rt : Runtime, compute : (K) -> V raise Failure, label? : String) -> DerivedMap[K, V]

Creates a keyed derived map.

#
DerivedMap::cache_len

fn[K, V] DerivedMap::cache_len(self : DerivedMap[K, V]) -> Int

Returns the number of cached entries.

#
DerivedMap::clear_cache

fn[K, V] DerivedMap::clear_cache(self : DerivedMap[K, V]) -> Unit

Clears all cached entries.

#
DerivedMap::fallible

fn[K : Hash + Eq, V, E] DerivedMap::fallible(rt : Runtime, compute : (K) -> Result[V, E], label? : String) -> DerivedMap[K, Result[V, E]]

Creates a fallible keyed derived map: a per-key recoverable domain failure is expressed in the value as Result[V, E], never raised. The compute is noraise. See Derived::fallible and docs/design/specs/2026-05-28-honest-read-error-ownership.md.

#
DerivedMap::get

fn[K : Hash + Eq, V : Eq] DerivedMap::get(self : DerivedMap[K, V], key : K) -> Result[V,
ReadError
]

Strict graph read for key. Requires an active tracked context. A private per-key entry disposed by runtime GC is recreated before reading.

#
DerivedMap::get_or_abort

fn[K : Hash + Eq, V : Eq] DerivedMap::get_or_abort(self : DerivedMap[K, V], key : K) -> V

Strict graph read for key that aborts on invalid context or any read error.

#
DerivedMap::has_cached

fn[K : Hash + Eq, V] DerivedMap::has_cached(self : DerivedMap[K, V], key : K) -> Bool

Returns whether a cached entry exists for key.

#
DerivedMap::read

fn[K : Hash + Eq, V : Eq] DerivedMap::read(self : DerivedMap[K, V], key : K) -> Result[V,
ReadError
]

Permissive read for key. Records a dependency if tracked. A private per-key entry disposed by runtime GC is recreated before reading; disposal of the map itself still returns ReadError::Disposed.

#
DerivedMap::read_or

fn[K : Hash + Eq, V : Eq] DerivedMap::read_or(self : DerivedMap[K, V], key : K, fallback : V) -> V

Returns the value for key, or fallback if a read error is detected.

#
DerivedMap::read_or_abort

fn[K : Hash + Eq, V : Eq] DerivedMap::read_or_abort(self : DerivedMap[K, V], key : K) -> V

Permissive read for key that aborts on any read error.

#
DerivedMap::read_or_else

fn[K : Hash + Eq, V : Eq] DerivedMap::read_or_else(self : DerivedMap[K, V], key : K, fallback : (
ReadError
) -> V) -> V

Returns the value for key, or computes a fallback from the read error.

#
DerivedMap::sweep_cache

fn[K : Hash + Eq, V] DerivedMap::sweep_cache(self : DerivedMap[K, V]) -> Int

Removes cached entries whose underlying cells have been disposed. Scope- owned maps are maintained by Scope::collect(); use this operation for raw maps and diagnostics.

#
EagerDerived

pub(all) struct EagerDerived[T] {
// private fields
}

User-facing push-mode eager derived cell.

Recomputed eagerly when any upstream cell changes. Value is cached; consecutive reads without upstream changes do not trigger recomputation.

#
EagerDerived::EagerDerived

fn[T : Eq] EagerDerived::EagerDerived(rt : Runtime, compute_fn : () -> T) -> EagerDerived[T]

Creates an eager derived value.

compute_fn is invoked immediately to set the initial value and establish dependencies. Whenever any dependency changes, the runtime calls compute_fn again via push propagation (triggered by Input::set or Runtime::batch).

Type Erasure

compute_fn is captured in a () -> Bool closure stored on PushReactiveData. The Bool indicates whether the value changed (enabling early cutoff for downstream push cells).

#
EagerDerived::dispose

fn[T] EagerDerived::dispose(self : EagerDerived[T]) -> Unit

Removes this eager derived cell from the dependency graph.

After disposal:
  • The cell is removed from all source subscriber sets.
  • cell_index[id] is set to Disposed.
  • The SoA slot is added to free_push_reactives for future reuse.

#
EagerDerived::expr

#alias(e)
fn[T] EagerDerived::expr(self : EagerDerived[T]) -> Expr[T]

#
EagerDerived::get

fn[T] EagerDerived::get(self : EagerDerived[T]) -> T

Returns the current cached value of the eager derived cell.

Must be called inside a tracked context (a memo or reactive compute function). Outside a compute function, use EagerDerived::read() or EagerDerived::watch().

#
EagerDerived::id

Returns the unique cell identifier for this eager derived value. Stable across reads — useful for graph-shape probes (gc anchoring, edge inspection) where a cell needs an identity independent of its value.

#
EagerDerived::is_disposed

fn[T] EagerDerived::is_disposed(self : EagerDerived[T]) -> Bool

Returns true if this eager derived cell has been disposed.

#
EagerDerived::read

fn[T] EagerDerived::read(self : EagerDerived[T]) -> T

Permissive read. Works outside the graph and records a dependency if tracked.

#
EagerDerived::watch

fn[T] EagerDerived::watch(self : EagerDerived[T]) -> Watch[T]

Creates a long-lived outside-graph reader for this eager derived value. Performs one priming read before returning.

#
Effect

pub struct Effect {
// private fields
}

User-facing push-mode effect.

Runs the provided function immediately on creation to establish dependencies, then re-runs whenever any dependency changes.

#
Effect::Effect

fn Effect::Effect(rt : Runtime, f : () -> Unit) -> Effect

Creates a new push-mode effect.

f is invoked immediately to establish dependencies and run the initial side effect. It is re-run by push propagation whenever any dependency changes.

#
Effect::dispose

fn Effect::dispose(self : Effect) -> Unit

Removes this effect from the dependency graph.

#
Effect::id

Returns the unique identifier for this effect cell.

#
Effect::is_disposed

fn Effect::is_disposed(self : Effect) -> Bool

Returns true if this effect has been disposed.

#
Effect::new

#deprecated("Use the constructor form `Effect(rt, f)` (`Effect::Effect`) instead.")
fn Effect::new(rt : Runtime, f : () -> Unit) -> Effect

Deprecated alias of the Effect::Effect constructor.

#
Expr

pub(all) struct Expr[T] {
// private fields
}

Lazy formula expression over target facade handles. Materializes to a single Derived[T] via Expr::derived; operator chains allocate no incremental cells.
impl Add for Expr[T]
impl Div for Expr[T]
impl Mod for Expr[T]
impl Mul for Expr[T]
impl Neg for Expr[T]
impl Sub for Expr[T]

#
Expr::constant

fn[T] Expr::constant(rt : Runtime, value : T, label? : String) -> Expr[T]

Creates a constant expression tied to an explicit runtime.

#
Expr::derived

fn[T : Eq] Expr::derived(self : Expr[T], label? : String) -> Derived[T]

Materializes this expression as one lazy Derived cell on its runtime.

#
Expr::map

fn[A, B] Expr::map(self : Expr[A], f : (A) -> B raise Failure) -> Expr[B]

#
Expr::map2

fn[A, B, C] Expr::map2(left : Expr[A], right : Expr[B], f : (A, B) -> C raise Failure) -> Expr[C]

#
Input

pub(all) struct Input[T] {
// private fields
} derive(
Debug
)

An input cell with an externally-set value.

Inputs are the leaves of the dependency graph — values you control directly. The T: Eq constraint enables same-value optimization: setting an input to its current value is a no-op (no revision bump, no downstream recomputation).

Example

let rt = Runtime()
let count = Input(rt, 0)
count.set(5)
inspect(count.get(), content="5")

#
Input::Input

fn[T] Input::Input(rt : Runtime, initial : T, durability? :
Durability
, label? : String) -> Input[T]

Creates a new input with the given initial value. Enables Input(rt, value) / Input(rt, value, label="...", durability=High) call sites.

Parameters

  • rt: The runtime that will manage this input
  • initial: The initial value of the input
  • durability: How often this input is expected to change (default: Low)
  • label: An optional human-readable name for debugging and cycle error output

Returns

A new input containing the initial value

Example

let count = Input(rt, 0)

let config = Input(rt, "prod", durability=High)

let named = Input(rt, 0, label="count")

#
Input::as_view

#alias("_[_:_]")
fn[T] Input::as_view(self : Input[T], start? : Unit, end? : Unit) -> InputView[T]

Returns a read-only view of this input.

input[:] is equivalent to input.as_view(). The view records dependencies through InputView::get() and cannot update or dispose the underlying input.

Example

let input = Input(rt, 42)
let view = input[:]
let doubled = Derived(rt, () => view.get() * 2)

input.set(21)
inspect(doubled.read_or_abort(), content="42")

#
Input::clear_on_change

fn[T] Input::clear_on_change(self : Input[T]) -> Unit

Removes the on_change callback for this input.

#
Input::derived

fn[T, U : Eq] Input::derived(self : Input[T], f : (T) -> U, label? : String) -> Derived[U]

Creates a new Derived[U] from this input by applying f to the current value on each read. Uses equality-based backdating: when recomputation produces a value equal to the previous output, downstream dependents skip recomputation.

Equivalent to scope.derived(() => f(input.get())) but without the scope parameter — the input already holds a runtime reference.

#
Input::derived2

fn[T, U, V : Eq] Input::derived2(self : Input[T], other : Input[U], f : (T, U) -> V, label? : String) -> Derived[V]

Creates a new Derived[V] by combining this input with another input. Uses equality-based backdating. Aborts if inputs belong to different runtimes.

#
Input::derived2_no_backdate

fn[T, U, V] Input::derived2_no_backdate(self : Input[T], other : Input[U], f : (T, U) -> V, label? : String) -> Derived[V]

Creates a new Derived[V] by combining this input with another input, without equality-based backdating. Accepts output types that do not implement Eq.

#
Input::derived3

fn[T, U, V, W : Eq] Input::derived3(self : Input[T], second : Input[U], third : Input[V], f : (T, U, V) -> W, label? : String) -> Derived[W]

Creates a new Derived[W] by combining this input with two other inputs. Uses equality-based backdating. Aborts if inputs belong to different runtimes.

#
Input::derived3_no_backdate

fn[T, U, V, W] Input::derived3_no_backdate(self : Input[T], second : Input[U], third : Input[V], f : (T, U, V) -> W, label? : String) -> Derived[W]

Creates a new Derived[W] by combining this input with two other inputs, without equality-based backdating. Accepts output types that do not implement Eq.

#
Input::derived_no_backdate

fn[T, U] Input::derived_no_backdate(self : Input[T], f : (T) -> U, label? : String) -> Derived[U]

Creates a new Derived[U] from this input by applying f to the current value on each read, without equality-based backdating. Each recomputation advances the changed-at revision unconditionally, even when the output equals the previous value. Accepts output types that do not implement Eq.

Equivalent to scope.derived_no_backdate(() => f(input.get())) but without the scope parameter — the input already holds a runtime reference.

#
Input::dispose

fn[T] Input::dispose(self : Input[T]) -> Unit

Disposes this input, releasing its resources and marking it as Disposed.

After disposal, calling get() or set() will abort. Disposal is idempotent — calling it multiple times is a no-op.

#
Input::durability

Returns the durability level of this input.

Durability indicates how often this input is expected to change:
  • High: Rarely changes (e.g., configuration)
  • Medium: Moderately stable
  • Low: Frequently changes (e.g., user input)

Returns

The durability level set at construction time

#
Input::expr

#alias(e)
fn[T] Input::expr(self : Input[T]) -> Expr[T]

#
Input::force_set

fn[T] Input::force_set(self : Input[T], new_value : T) -> Unit

Sets the input to a new value, always bumping the revision.

Unlike set, this does not check for equality. Use this when you want to force downstream deriveds to reverify even if the value is the same, or when your type doesn't implement Eq.

During a batch, the value is stored as pending and committed at batch end.

Parameters

  • new_value: The new value to set

#
Input::get

fn[T] Input::get(self : Input[T]) -> T

Returns the current value of the input.

If called inside a derived's compute function, this automatically records a dependency from the derived to this input. When the input changes, the derived will know to reverify.

Returns

The current value of the input

#
Input::get_result

fn[T] Input::get_result(self : Input[T]) -> Result[T,
ReadError
]

Returns the current value of the input as a Result.

Unlike get() which aborts on disposal, this method returns Err(Disposed(cell_id)) when the input has been disposed. Cycle errors cannot occur for inputs (they have no dependencies), so the Cycle variant of ReadError is structurally unreachable — but accepting it through the shared ReadError type keeps the channel uniform across all cell types.

Returns

Ok(value) with the current value of the input, or Err(ReadError::Disposed(id)) if the input has been disposed

#
Input::id

Returns the unique identifier for this input.

The CellId can be used with Runtime::cell_info() to retrieve metadata, or to compare cell identities.

Returns

The cell identifier for this input

Example

let inp = Input(rt, 42)
let id = inp.id()
match rt.cell_info(id) {
Some(info) => println("Input changed at: " + info.changed_at.to_string())
None => ()
}

#
Input::is_disposed

fn[T] Input::is_disposed(self : Input[T]) -> Bool

Returns true if this input has been disposed.

#
Input::is_fresh

fn[T] Input::is_fresh(self : Input[T]) -> Bool

Returns true. Inputs are directly-set cells and are always fresh.

#
Input::is_up_to_date

fn[T] Input::is_up_to_date(self : Input[T]) -> Bool

Returns true. Inputs are always up-to-date since they are input cells with directly-set values.

#
Input::new

#deprecated("Use the constructor form `Input(rt, initial)` (`Input::Input`) instead.")
fn[T] Input::new(rt : Runtime, initial : T, durability? :
Durability
, label? : String) -> Input[T]

Deprecated alias of the Input::Input constructor.

#
Input::on_change

fn[T] Input::on_change(self : Input[T], f : (T) -> Unit) -> Unit

Registers a callback that fires whenever this input's value changes.

The callback receives the new value. It fires after the value is updated but before Runtime::fire_on_change(). Only one callback can be registered at a time; calling this again replaces the previous callback.

Note: When using force_set, the callback fires even if the new value equals the current value, since force_set bypasses the equality check.

Parameters

  • f: Called with the new value whenever this input changes

#
Input::peek

fn[T] Input::peek(self : Input[T]) -> T

Returns the current value of the input without recording a dependency.

Use peek() to read an input's value from outside the dependency graph (e.g., in event handlers, logging, or tests). Unlike get(), this never records a dependency even when called inside a compute function.

Returns

The current value of the input

#
Input::set

fn[T : Eq] Input::set(self : Input[T], new_value : T) -> Unit

Sets the input to a new value.

If the new value equals the current value (via Eq), this is a no-op: no revision bump occurs, and downstream deriveds won't reverify.

During a batch (Runtime::batch), the write is deferred. At batch end, only inputs whose final value differs from the pre-batch value trigger a revision bump. This enables revert detection.

Parameters

  • new_value: The new value to set

Same-Value Optimization

let s = Input(rt, 5)
s.set(5) // No-op: value unchanged, no revision bump
s.set(6) // Bumps revision, deriveds depending on s will reverify

#
InputField

pub(all) struct InputField[T] {
// private fields
} derive(
Debug
)

Target-name field-level input cell — a thin wrapper around Input[T].

InputField provides the same functionality as Input but is intended for use cases where you want a more structured, named input cell (e.g. fields of a tracked struct). All operations delegate directly to the inner Input.

Example

let rt = Runtime()
let field = InputField(rt, 0, label="counter")
field.set(5)
inspect(field.get(), content="5")

#
InputField::InputField

fn[T] InputField::InputField(rt : Runtime, initial : T, durability? :
Durability
, label? : String) -> InputField[T]

Creates a field-level input cell with the given initial value.

Parameters

  • rt: The runtime that will manage this cell
  • initial: The initial value of the field
  • durability: How often this field is expected to change (default: Low)
  • label: An optional human-readable name for debugging and cycle error output

Returns

A new InputField containing the initial value

Example

let field = InputField(rt, 42)

let config = InputField(rt, "prod", durability=High)

let named = InputField(rt, 0, label="counter")

#
InputField::as_view

#alias("_[_:_]")
fn[T] InputField::as_view(self : InputField[T], start? : Unit, end? : Unit) -> InputView[T]

Returns a read-only view of this input field.

field[:] is equivalent to field.as_view() and returns a view of the field's underlying input cell.

#
InputField::clear_on_change

fn[T] InputField::clear_on_change(self : InputField[T]) -> Unit

Removes this field's on_change callback.

#
InputField::dispose

fn[T] InputField::dispose(self : InputField[T]) -> Unit

Disposes this field by disposing its inner input.

#
InputField::durability

Returns the durability level of this field.

Durability indicates how often this field is expected to change:
  • High: Rarely changes (e.g., configuration)
  • Medium: Moderately stable
  • Low: Frequently changes (e.g., user input)

Returns

The durability level set at construction time

#
InputField::expr

#alias(e)
fn[T] InputField::expr(self : InputField[T]) -> Expr[T]

#
InputField::force_set

fn[T] InputField::force_set(self : InputField[T], new_value : T) -> Unit

Sets the field to a new value, always bumping the revision.

Unlike set, this does not check for equality. Use this when you want to force downstream deriveds to reverify even if the value is the same, or when your type doesn't implement Eq.

Parameters

  • new_value: The new value to set

#
InputField::get

fn[T] InputField::get(self : InputField[T]) -> T

Returns the current value of the field.

If called inside a derived's compute function, this automatically records a dependency from the derived to this cell. When the field changes, the derived will know to reverify.

Returns

The current value of the field

#
InputField::get_result

fn[T] InputField::get_result(self : InputField[T]) -> Result[T,
ReadError
]

Returns the current value of the field as a Result.

Like Input::get_result, this method returns Err(Disposed(id)) when the field has been disposed, instead of aborting. Cycle errors cannot occur for input fields (they have no dependencies).

Returns

Ok(value) with the current value of the field, or Err(ReadError::Disposed(id)) if the field has been disposed

#
InputField::id

Returns the unique identifier for this field.

The CellId can be used with Runtime::cell_info() to retrieve metadata, or to compare cell identities.

Returns

The cell identifier for this field

#
InputField::is_disposed

fn[T] InputField::is_disposed(self : InputField[T]) -> Bool

Returns true if this field has been disposed.

#
InputField::is_fresh

fn[T] InputField::is_fresh(self : InputField[T]) -> Bool

Returns true. Input fields are directly-set cells and are always fresh.

#
InputField::on_change

fn[T] InputField::on_change(self : InputField[T], f : (T) -> Unit) -> Unit

Registers a callback that fires whenever this field's value changes.

The callback receives the new value. Only one callback can be registered at a time; calling this again replaces the previous callback.

Parameters

  • f: Called with the new value whenever this field changes

#
InputField::peek

fn[T] InputField::peek(self : InputField[T]) -> T

Returns the current value of the field without recording a dependency.

Delegates to Input::peek() on the inner input. Use from outside the dependency graph when you don't want to trigger recomputation.

#
InputField::set

fn[T : Eq] InputField::set(self : InputField[T], new_value : T) -> Unit

Sets the field to a new value.

If the new value equals the current value (via Eq), this is a no-op: no revision bump occurs, and downstream deriveds won't reverify.

During a batch (Runtime::batch), the write is deferred. At batch end, only cells whose final value differs from the pre-batch value trigger a revision bump.

Parameters

  • new_value: The new value to set

#
InputView

pub(all) struct InputView[T] {
// private fields
}

A read-only view of an input cell.

Obtain an InputView with input[:] or input.as_view(). The view reads the same cell as its source, but does not expose write, callback, or disposal authority.

#
InputView::get

fn[T] InputView::get(self : InputView[T]) -> T

Returns the current value and records a dependency in a reactive compute.

This follows the same strict lifecycle contract as Input::get().

#
InputView::peek

fn[T] InputView::peek(self : InputView[T]) -> T

Returns the current value without recording a dependency.

This follows the same strict lifecycle contract as Input::peek().

#
MapRelation

pub(all) struct MapRelation[K, V] {
// private fields
}

A Datalog functional relation: a key-value map with delta tracking for fixpoint evaluation.

Unlike Relation[T] (a HashSet), MapRelation[K, V] uses @hashmap.HashMap[K, V] and supports replacing values for existing keys.

Three layers:
  • current — materialized post-drain (readable via get()/iter())
  • delta — frontier for current fixpoint iteration
  • staged_delta — staging buffer during fixpoint

  • insert() adds to frontier delta outside fixpoint, staged delta during fixpoint
  • get() reads current (the materialized post-drain map)
  • iter() iterates current; records a dependency for pull verification
  • delta_iter() iterates delta; used by rule bodies

#
MapRelation::MapRelation

fn[K : Hash + Eq, V] MapRelation::MapRelation(rt : Runtime, merge? : (V, V) -> V, label? : String) -> MapRelation[K, V]

Creates a relation-shaped map input.

#
MapRelation::delta_iter

fn[K, V] MapRelation::delta_iter(self : MapRelation[K, V]) -> Iter[(K, V)]

Iterates over the delta set (new/updated entries not yet drained to current).

Used by rule bodies to read only the new entries produced in the previous fixpoint iteration. All map relation reads abort after disposal.

#
MapRelation::dispose

fn[K, V] MapRelation::dispose(self : MapRelation[K, V]) -> Unit

Disposes this map relation, clearing its maps and marking it as Disposed.

A live rule pins every declared input and output relation. Disposal aborts until those rules are disposed. Repeated disposal is a no-op after the map relation is disposed; all current and delta reads abort after disposal.

#
MapRelation::get

fn[K : Hash + Eq, V] MapRelation::get(self : MapRelation[K, V], key : K) -> V?

Looks up a key in the current (materialized) map.

Values in delta are NOT visible via get until after fixpoint() drains them. Like iter(), this records a dependency for pull verification.

#
MapRelation::id

Returns the CellId for this map relation.

#
MapRelation::insert

fn[K : Hash + Eq, V : Eq] MapRelation::insert(self : MapRelation[K, V], key : K, value : V) -> Bool

Inserts a key-value pair into the delta set.

Outside fixpoint(), inserts go to the current frontier delta. During fixpoint(), inserts go to the staged delta for the next iteration.

If a merge function is provided, it is applied when the key already has an effective value: merge(old, new). If the merged result equals the old effective value, the insert is a no-op.

Returns true if the effective value changed, false if it was a no-op.

#
MapRelation::is_disposed

fn[K, V] MapRelation::is_disposed(self : MapRelation[K, V]) -> Bool

Returns true if this map relation has been disposed.

#
MapRelation::iter

fn[K, V] MapRelation::iter(self : MapRelation[K, V]) -> Iter[(K, V)]

Iterates over the current (materialized) map as (key, value) pairs.

Records a dependency so pull memos that call iter() automatically re-verify when the map relation changes after a fixpoint().

#
ReachableDerived

pub(all) struct ReachableDerived[T] {
// private fields
} derive(
Debug
)

Reachable lazy derived facade. Same revision-based verification as Derived, and additionally participates in push reachability and Watch/GC lifetimes for downstream eager subscribers.

#
ReachableDerived::ReachableDerived

fn[T : Eq] ReachableDerived::ReachableDerived(rt : Runtime, compute : () -> T raise Failure, label? : String) -> ReachableDerived[T]

Creates a reachable lazy derived value.

#
ReachableDerived::dispose

fn[T] ReachableDerived::dispose(self : ReachableDerived[T]) -> Unit

Disposes this reachable derived value, freeing associated resources. After disposal, reads abort with Disposed.

#
ReachableDerived::expr

#alias(e)
fn[T : Eq] ReachableDerived::expr(self : ReachableDerived[T]) -> Expr[T]

#
ReachableDerived::get

Strict graph read. Requires an active tracked context.

#
ReachableDerived::get_or_abort

fn[T : Eq] ReachableDerived::get_or_abort(self : ReachableDerived[T]) -> T

Strict graph read that aborts on invalid context or any read error.

#
ReachableDerived::id

Returns the unique cell identifier for this reachable derived value. Stable across reads — useful for graph-shape probes (gc anchoring, edge inspection) where a cell needs an identity independent of its value.

#
ReachableDerived::is_disposed

fn[T] ReachableDerived::is_disposed(self : ReachableDerived[T]) -> Bool

Returns true if this reachable derived cell has been disposed.

#
ReachableDerived::is_fresh

fn[T] ReachableDerived::is_fresh(self : ReachableDerived[T]) -> Bool

Returns whether this reachable derived value is verified at the current revision.

#
ReachableDerived::read

Permissive read. Works outside the graph and records a dependency if tracked.

#
ReachableDerived::read_or_abort

fn[T : Eq] ReachableDerived::read_or_abort(self : ReachableDerived[T]) -> T

Permissive read that aborts on any read error.

#
ReachableDerived::watch

fn[T : Eq] ReachableDerived::watch(self : ReachableDerived[T]) -> Watch[T]

Creates a GC-safe long-lived outside-graph reader that returns read errors.

Performs one priming read so upstream gc_dependencies are recorded before return — a Runtime::gc() that runs before the first consumer read cannot sweep the upstream graph. A priming read error remains observable through Watch::read(). See Scope::watch for the scope-owned variant.

#
Relation

pub(all) struct Relation[T] {
// private fields
}

A Datalog relation: a set of tuples with delta tracking for fixpoint evaluation.

Relation[T] holds typed current and delta sets via Ref. The Runtime sees only type-erased closures on RelationData.

  • insert() adds to frontier delta outside fixpoint, and to staged delta during fixpoint
  • contains() checks current (the materialized post-drain set)
  • iter() iterates current; records a dependency for pull verification
  • delta_iter() iterates delta; used by rule bodies

#
Relation::Relation

fn[T : Hash + Eq] Relation::Relation(rt : Runtime, label? : String) -> Relation[T]

Creates a new relation. Enables Relation(rt) / Relation(rt, label="...") call sites.

#
Relation::contains

fn[T : Hash + Eq] Relation::contains(self : Relation[T], value : T) -> Bool

Checks whether a fact exists in the current (materialized) set.

Facts in delta are NOT visible via contains until after fixpoint() drains them. Like iter(), this records a dependency for pull verification.

#
Relation::delta_iter

fn[T] Relation::delta_iter(self : Relation[T]) -> Iter[T]

Iterates over the delta set (new facts not yet drained to current).

Used by rule bodies to read only the new facts produced in the previous fixpoint iteration. All relation reads abort after disposal.

#
Relation::dispose

fn[T] Relation::dispose(self : Relation[T]) -> Unit

Disposes this relation, clearing its fact sets and marking it as Disposed.

A live rule pins every declared input and output relation. Disposal aborts until those rules are disposed. Repeated disposal is a no-op after the relation is disposed; all current and delta reads abort after disposal.

#
Relation::id

Returns the CellId for this relation.

#
Relation::insert

fn[T : Hash + Eq] Relation::insert(self : Relation[T], value : T) -> Bool

Inserts a fact into the delta set.

Outside fixpoint(), inserts go to the current frontier delta. During fixpoint(), inserts go to the staged delta for the next iteration.

Returns true if the fact was newly added, false if it already exists.

#
Relation::is_disposed

fn[T] Relation::is_disposed(self : Relation[T]) -> Bool

Returns true if this relation has been disposed.

#
Relation::iter

fn[T] Relation::iter(self : Relation[T]) -> Iter[T]

Iterates over the current (materialized) set.

Records a dependency so pull memos that call iter() automatically re-verify when the relation changes after a fixpoint().

#
Relation::new

#deprecated("Use the constructor form `Relation(rt)` (`Relation::Relation`) instead.")
fn[T : Hash + Eq] Relation::new(rt : Runtime, label? : String) -> Relation[T]

Deprecated alias of the Relation::Relation constructor.

#
Runtime

pub(all) struct Runtime {
// private fields
}

Central coordinator for the incremental computation framework.

The Runtime manages all bookkeeping for dependency tracking, revision counting, and batch operations. Every Input and Derived is associated with exactly one Runtime.

Pointer chase tradeoff

All sub-struct field accesses (e.g. self.core.revision.current_revision) require one extra pointer dereference compared to the old flat layout. MoonBit structs are heap-allocated, so RuntimeCore is a separate heap object. In hot loops (verify dep-walk, push propagation), the sub-struct pointers are cache-hot since they are dereferenced on every iteration.

Example

let rt = Runtime()

let x = Input(rt, 10)

let doubled = Memo(rt, () => x.get() * 2)

#
Runtime::Runtime

fn Runtime::Runtime(on_change? : () -> Unit) -> Runtime

Creates a new runtime with an empty dependency graph. Enables Runtime() / Runtime(on_change=...) call sites.

Parameters

  • on_change: Optional callback invoked whenever any input changes (or at the end of a batch if values actually changed).

#
Runtime::add_derived_event_listener

fn Runtime::add_derived_event_listener(self : Runtime, f : (DerivedEvent) -> Unit) ->
ListenerId
raise Failure

Adds a composable derived-event listener and returns its ListenerId.

Multiple additive listeners (and the singleton) coexist on one runtime and fire event-major in registration order. Remove a specific listener with remove_derived_event_listener. Raises Failure unless the runtime is between operations (same idle guard as on_derived_event): the derived-event hook buffers events, so registration must not race a buffered/draining window.

#
Runtime::add_on_change_listener

fn Runtime::add_on_change_listener(self : Runtime, f : () -> Unit) ->
ListenerId

Adds a composable on-change listener and returns its ListenerId.

Multiple additive listeners (and the singleton) coexist and fire in registration order on every revision bump. Remove a specific listener with remove_on_change_listener. Not phase-guarded (see set_on_change).

#
Runtime::batch

fn Runtime::batch(self : Runtime, f : () -> Unit raise?) -> Unit raise?

Executes a closure with batched input updates.

All Input::set calls inside the closure are deferred. At batch end, a single revision bump occurs for all changes. This provides:

  • Atomicity: Memos see either all changes or none
  • Efficiency: One verification pass instead of many
  • Revert detection: Setting an input back to its original value is a no-op

Batches can be nested. Only the outermost batch commits changes.

Parameters

  • f: The closure to execute If f raises an error, pending writes in this batch are rolled back and the error is re-raised to the caller.

Example

rt.batch(() => {
x.set(10)
y.set(20)
z.set(30)
})
// Single revision bump for all three changes

Revert Detection

rt.batch(() => {
x.set(5) // Change from 0 to 5
x.set(0) // Change back to 0
})
// No revision bump — net change is zero

Abort Behavior

MoonBit abort() is not catchable. If f aborts (rather than raises), cleanup cannot run and runtime state may be left inconsistent.

#
Runtime::batch_result

fn Runtime::batch_result(self : Runtime, f : () -> Unit raise) -> Result[Unit, Error]

Executes a batch and returns raised errors as Result instead of re-raising.

This is a convenience wrapper around Runtime::batch for callers that prefer explicit result handling. Like Runtime::batch, this captures raised errors only; abort() is not recoverable here and still escapes Runtime::batch_result.

#
Runtime::cell_info

Returns structured metadata for any cell (input or derived).

This method provides uniform introspection access to cell metadata regardless of whether the cell is an Input or Memo. It returns CellInfo containing the cell's ID, type, revision information, durability, and dependency list.

Parameters

  • id: The CellId to query (obtained via Input::id() or Memo::id())

Returns

  • Some(CellInfo): If the cell ID is valid and points to an active cell
  • None: If the cell ID is out of bounds or points to an unused slot

Usage

let rt = Runtime()
let sig = Input(rt, 42)
let derived = Derived(rt, fn() { sig.get() * 2 })
let _ = derived.read_or_abort() // Force computation

// Query input metadata
match rt.cell_info(sig.id()) {
Some(info) => {
println("Input changed at: \{info.changed_at}")
println("Dependencies: \{info.dependencies.length()}")
}
None => println("Cell not found")
}

// Query derived metadata
match rt.cell_info(derived.id()) {
Some(info) => {
println("Derived durability: \{info.durability}")
println("Depends on \{info.dependencies.length()} cells")
}
None => println("Cell not found")
}

Notes

  • The dependency array is a copy; modifying it does not affect the runtime
  • For inputs, the dependencies array is always empty
  • For derived cells, dependencies are populated after the first computation
  • The method performs bounds checking and returns None for invalid IDs

#
Runtime::clear_derived_event_listener

fn Runtime::clear_derived_event_listener(self : Runtime) -> Unit raise Failure

Clears the singleton derived recompute lifecycle event listener. Idempotent; additive listeners are unaffected. Raises Failure unless the runtime is between operations.

#
Runtime::clear_on_change

fn Runtime::clear_on_change(self : Runtime) -> Unit

Removes the singleton on-change callback. Idempotent; additive listeners are unaffected.

#
Runtime::dependents

Returns the cell IDs that depend on the given cell (reverse edges).

This enables introspection of the dependency graph in both directions. The returned array is a snapshot; modifying it does not affect the runtime.

Returns an empty array if the cell ID is invalid, disposed, out of bounds, or belongs to a different runtime — matching cell_info semantics.

Parameters

  • id: The cell to query

Returns

Array of CellIds that have id in their dependency list, or empty array if the cell ID is not valid for this runtime or has been disposed

#
Runtime::dispose_cell

fn Runtime::dispose_cell(self : Runtime, cell_id :
CellId
) -> Unit

#
Runtime::dispose_rule

fn Runtime::dispose_rule(self : Runtime, rule_id :
RuleId
) -> Unit

Disposes a rule cell. Public because RuleId has no runtime reference, so the user must call rt.dispose_rule(rule_id) directly.

#
Runtime::fixpoint

fn Runtime::fixpoint(self : Runtime) -> Unit

Runs fixpoint evaluation and publishes any resulting cell changes.

#
Runtime::gc

fn Runtime::gc(self : Runtime) -> Unit

Runs mark-and-sweep garbage collection on the dependency graph.

Dispose dispatch is injected as a closure closing over self.dispose_cell, so per-kind CellLifecycle dispatch remains reachable from the kernel sweep loop. Collection aborts unless the runtime is between operations, including callback and event-drain boundaries.

#
Runtime::gc_root_count

fn Runtime::gc_root_count(self : Runtime, id :
CellId
) -> Int

Number of GC roots currently anchoring id — i.e. how many Watch handles or gc_root = Root cells reference it. Returns 0 for unknown or disposed cells (matches the Runtime::dependents soft-fail idiom).

Phase 1 coordinator use: registration-time mode-1 check that each protected cell is watch-rooted before the editor is published.

#
Runtime::id

Returns this runtime's identity.

Use it to ask "are these two runtimes the same?" (a.id() == b.id()) without allocating a probe cell to read a CellId's runtime_id. A RuntimeId is a debug / introspection identity, not a stable application key — see RuntimeId.

#
Runtime::input

fn[T] Runtime::input(self : Runtime, initial : T, durability? :
Durability
, label? : String) -> Input[T]

Creates an input cell owned by this runtime.

#
Runtime::new

#deprecated("Use the constructor form `Runtime()` (`Runtime::Runtime`) instead.")
fn Runtime::new(on_change? : () -> Unit) -> Runtime

Deprecated alias of the Runtime::Runtime constructor.

#
Runtime::new_rule

fn Runtime::new_rule(self : Runtime, input_relations : Array[
CellId
], output_relations : Array[
CellId
], apply_delta : () -> Unit, label? : String) ->
RuleId

Registers a Datalog rule over declared input and output relations.

The declaration arrays are snapshotted. A live rule pins every relation it declares, so dispose each declaring rule before disposing those relations. Panics if a declared cell belongs to another runtime, is disposed, or is not a relation.

#
Runtime::on_derived_event

fn Runtime::on_derived_event(self : Runtime, f : (DerivedEvent) -> Unit) -> Unit raise Failure

Registers the singleton derived recompute lifecycle event listener.

Re-registering replaces the previous singleton listener in place (its registration position is preserved). Coexists with any additive listeners registered via add_derived_event_listener. Raises Failure unless the runtime is between operations (see is_listener_mutation_safe).

#
Runtime::record_batch_rollback

fn Runtime::record_batch_rollback(self : Runtime, cell_id :
CellId
, rollback : () -> Unit) -> Unit

Registers a rollback action for the current batch frame.

Only the first rollback registration per cell_id in the frame is stored. If not currently in a batch, this is a no-op.

#
Runtime::remove_derived_event_listener

fn Runtime::remove_derived_event_listener(self : Runtime, id :
ListenerId
) -> Unit raise Failure

Removes the additive derived-event listener with id. Idempotent — an unknown or already-removed id is a no-op. Raises Failure unless the runtime is between operations (same idle guard rationale as registration).

#
Runtime::remove_on_change_listener

fn Runtime::remove_on_change_listener(self : Runtime, id :
ListenerId
) -> Unit

Removes the additive on-change listener with id. Idempotent — an unknown or already-removed id is a no-op.

#
Runtime::set_on_change

fn Runtime::set_on_change(self : Runtime, f : () -> Unit) -> Unit

Registers the singleton on-change callback that fires whenever a revision bump occurs.

Re-registering replaces the previous singleton in place (registration position preserved). Coexists with additive listeners registered via add_on_change_listener. Unlike the derived-event hook, on-change has no buffer/drain state and is read as a snapshot at one well-defined point, so registration is not phase-guarded.

#
Scope

pub struct Scope {
// private fields
}

Hierarchical cell ownership with bulk disposal.

Scope owns cells and child scopes. Disposing a scope disposes all owned cells and children recursively. This enables UI component lifecycle patterns: create cells during mount, dispose the scope on unmount.

Disposal Order

  1. Children (bottom-up, recursively)
  2. Dispose hooks (watches and Scope::on_dispose cleanups)
  3. Owned cells

Example

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()
impl Debug for Scope

#
Scope::accepted_derived

fn[V : Eq, E : Eq] Scope::accepted_derived(self : Scope, compute : () -> Result[V, E], label? : String) -> AcceptedDerived[V, E]

Scope-owned convenience, mirroring Scope::derived. The returned AcceptedDerived lives in a child scope, so disposing self disposes it.

#
Scope::accepted_memo

fn[V :
BackdateEq
+
HasChangedAt
, E : Eq] Scope::accepted_memo(self : Scope, compute : () -> Result[V, E], label? : String) -> AcceptedDerived[V, E]

BackdateEq companion of Scope::accepted_derived: scope-owned, accepts by revision identity. Requires V : BackdateEq, E : Eq.

#
Scope::accumulator

fn[T : Eq] Scope::accumulator(self : Scope, label? : String) -> Accumulator[T]

Creates an accumulator owned by this scope. The accumulator is disposed automatically when the scope is disposed (via dispose_hooks).

#
Scope::add_cell_ids

fn Scope::add_cell_ids(self : Scope, ids : Array[
CellId
]) -> Unit

Registers an array of CellIds with this scope for bulk disposal.

When the scope is disposed, all registered cells are disposed. This is the low-level building block for add_input_fields(scope, owner).

#
Scope::add_watch

fn[T] Scope::add_watch(self : Scope, watch : Watch[T]) -> Watch[T]

Registers a watch with this scope for automatic disposal.

When the scope is disposed, the watch is disposed in the dispose_hooks phase (step 2 of disposal order — after children, before owned cells).

Returns the watch for immediate use.

#
Scope::adopt

fn[T : InputFieldOwner] Scope::adopt(self : Scope, tracked : T) -> T

Adopts all cells from an InputFieldOwner into this scope for bulk disposal.

This is the method-style version of add_input_fields(scope, owner). Use it to register cells that were created outside a scope (e.g. via map or raw constructors) with a scope's lifecycle.

Returns the trackable value for convenient chaining.

Example

let scope = Scope::new(rt)
let base = scope.derived(fn() { 42 })
let d = base.map(fn(v) { v + 1 })
scope.adopt(d)
scope.dispose() // d is disposed

#
Scope::child

fn Scope::child(self : Scope) -> Scope

Creates a child scope owned by this scope.

Disposing the parent will dispose this child first (bottom-up order).

#
Scope::collect

fn Scope::collect(self : Scope) -> Unit

Runs runtime-wide graph garbage collection, then retires GC-disposed DerivedMap entries owned by this scope and its live descendants.

Call after reading the attachment's terminal Watch at a caller-selected idle point. Aborts if this scope is disposed or collection is not legal for the runtime's current phase. Disposed child ownership records are released before traversing the live subtree. Repeated calls are idempotent.

#
Scope::derived

fn[T : Eq] Scope::derived(self : Scope, f : () -> T raise Failure, label? : String) -> Derived[T]

Creates a lazy derived facade owned by this scope.

#
Scope::derived_expr

fn[T : Eq] Scope::derived_expr(self : Scope, expr : Expr[T], label? : String) -> Derived[T]

Materializes an expression as a lazy derived cell owned by this scope. Aborts if expr belongs to a different runtime.

#
Scope::derived_map

fn[K : Hash + Eq, V] Scope::derived_map(self : Scope, compute : (K) -> V raise Failure, label? : String) -> DerivedMap[K, V]

Creates a keyed derived facade whose cached entries are retired by Scope::collect() and cleared on scope disposal.

#
Scope::derived_no_backdate

fn[T] Scope::derived_no_backdate(self : Scope, f : () -> T raise Failure, label? : String) -> Derived[T]

Creates a lazy derived facade owned by this scope, without equality-based backdating. Each recomputation advances the changed-at revision unconditionally, even when the output equals the previous value. Accepts output types that do not implement Eq.

#
Scope::dispose

fn Scope::dispose(self : Scope) -> Unit

Disposes this scope: child scopes first, then hooks, then owned cells. Scope is closed before any disposal effects run, so re-entrant dispose calls are no-ops once teardown starts. Idempotent — disposing an already-closed scope is a no-op.

#
Scope::eager_derived

fn[T : Eq] Scope::eager_derived(self : Scope, compute_fn : () -> T) -> EagerDerived[T]

Creates an eager derived facade owned by this scope.

#
Scope::effect

fn Scope::effect(self : Scope, f : () -> Unit) -> Effect

Creates an effect owned by this scope.

#
Scope::input

fn[T] Scope::input(self : Scope, initial : T, durability? :
Durability
, label? : String) -> Input[T]

Creates an input facade owned by this scope.

#
Scope::input_field

fn[T] Scope::input_field(self : Scope, initial : T, durability? :
Durability
, label? : String) -> InputField[T]

Creates an input-field facade owned by this scope.

#
Scope::is_disposed

fn Scope::is_disposed(self : Scope) -> Bool

Returns true if this scope has been disposed.

#
Scope::new

fn Scope::new(rt : Runtime) -> Scope

Creates a new root scope.

#
Scope::on_dispose

fn Scope::on_dispose(self : Scope, cleanup : () -> Unit) -> Unit

Registers a cleanup callback to run when this scope is disposed. The callback runs at most once, during the dispose_hooks phase (step 2 of disposal order — after children, before owned cells). It does not run if the scope is already closed, and registration on a closing/closed scope (including teardown-in-progress) aborts.

This is the intended hook for releasing resources that were acquired at mount time, such as removing runtime-level listeners:

let scope = Scope::new(rt)
let listener_id = rt.add_on_change_listener(() => sync())
scope.on_dispose(() => {
rt.remove_on_change_listener(listener_id)
})

#
Scope::reachable_derived

fn[T : Eq] Scope::reachable_derived(self : Scope, f : () -> T raise Failure, label? : String) -> ReachableDerived[T]

Creates a reachable lazy derived facade owned by this scope.

#
Scope::watch

fn[T] Scope::watch(self : Scope, derived : Derived[T]) -> Watch[T]

Creates a GC-safe watch on a derived value, owned by this scope.

Folds watch creation and scope registration into one call. Derived::watch performs the priming read that records upstream gc_dependencies, so the graph survives Runtime::gc() before the first consumer read. A priming read error remains observable through Watch::read().

#
Scope::watch_reachable

fn[T : Eq] Scope::watch_reachable(self : Scope, derived : ReachableDerived[T]) -> Watch[T]

Creates a GC-safe watch on a reachable derived value, owned by this scope.

Folds watch creation and scope registration into one call. The priming read is performed by ReachableDerived::watch(). MoonBit does not allow a second Scope::watch overload; use this for ReachableDerived and Scope::watch for Derived.

#
Watch

pub struct Watch[T] {
// private fields
} derive(
Debug
)

A typed persistent read root for values read outside the graph.

A Watch keeps its target reachable across Runtime::gc() and preserves read errors as values.

#
Watch::dispose

fn[T] Watch::dispose(self : Watch[T]) -> Unit

Releases this watch's keep-alive hold on the target cell.

Idempotent: disposing an already-disposed watch is a no-op.

#
Watch::is_disposed

fn[T] Watch::is_disposed(self : Watch[T]) -> Bool

Returns true if this watch has been disposed.

#
Watch::read

fn[T] Watch::read(self : Watch[T]) -> Result[T,
ReadError
]

Returns the current value or a mechanism error (cycle / disposed) from the watched cell.

#
Watch::read_or_abort

fn[T] Watch::read_or_abort(self : Watch[T]) -> T

Returns the current value, aborting if a read error is detected.