Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Language state

State retained by each Evaluator gives every stateful operation a lifetime independent of immutable StreamProgram sharing. Lifting, conditional branches, calls, and recursion each retain state at the operation occurrence that owns them.

Lifting state

Ordinary operators lift over NoVal and Deferred according to their semantics. The evaluator stores operand and result state where an operation must combine values across sparse ticks. This state is indexed by graph-local NodeId inside one evaluator; an equal operation in another stream or call site has a separate owner.

Conditional timelines

Conditional evaluation in ordinary and recursive contexts Ordinary conditionals evaluate both branch EvaluationGraphs so both persistent EvaluatorStates advance before one result is selected. In recursive call context the condition first retains the last Boolean across NoVal. An effective Boolean evaluates only its selected branch; Deferred, or NoVal without a retained Boolean, evaluates neither branch. Reconfigurable expressions inside lazy branches are rejected during compilation. Lazy if: ordinary timelines and recursive base cases condition retained stream-lifted value then EvaluationGraph own persistent EvaluatorState else EvaluationGraph own persistent EvaluatorState ordinary if advance both; select one result recursive call context genuinely lazy selection effective Bool current or retained; select one branch Deferred or unretained NoVal evaluate neither branch Reconfigurable expressions inside either lazy branch are rejected during compilation

Reading rule. Ordinary conditionals advance both persistent branch evaluators before selecting a result. In recursive call context, the condition first applies retained lifting state: a Value::NoVal after an earlier Boolean can reuse that Boolean and advance only its selected branch. Value::Deferred, or an effective Value::NoVal with no retained Boolean, advances neither branch. Runtime-defined expressions are rejected inside lazy recursive branches.

This difference is part of language semantics, not an optimization. Branch-local delays and calls therefore have timelines determined by the applicable conditional form.

For if condition then x + 1 else y + 2, both sums advance even when only one is selected. After retention, an uninitialized NoVal branch makes the result NoVal; an unselected Deferred branch does not make the selected concrete result deferred. Recursive conditionals retain their lazy canonical execution policy. These rules are distinct from strict binary propagation, where NoVal takes precedence over Deferred after retaining each operand.

Function binding

A function definition separates immutable body meaning from invocation state. Binding resolves captures to outer environment slots, places captures before parameters in a local layout, and packages the bound body as a shared StreamProgram.

Current StreamFunction binding structures Binding removes parameters from an UnboundFunction's free variables, resolves captures to outer EnvironmentSlots, binds the EvaluationGraph against a captures-first local EnvironmentLayout, and stores the resulting StreamProgram in StreamFunction. Bind UnboundFunction into StreamFunction UnboundFunction parameters + EvaluationGraph free vars - parameters resolve capture_slots StreamFunction parameters + display + program capture_slots Vec<EnvironmentSlot> in outer row Rc<StreamProgram> bound EvaluationGraph local EnvironmentLayout [captures | parameters]

Reading rule. Capture mapping and local slot order are immutable. Mutable body state is created by the evaluator that invokes the shared program.

Calls and recursion

A persistent call site retains one callable evaluator while the active function identity remains the same. Changing function identity creates fresh call-site state.

Recursive calls cannot share one mutable evaluator across active depths. They use a frame pool: each active depth acquires a frame with its own local row and evaluator state, resets it for that invocation, evaluates, and returns the frame to the pool as recursion unwinds.

Normal Apply state continuation and recursive frame evaluation Normal Apply retains one callable instance while function identity F1 remains active. Its evaluator state advances from S n on one logical tick to S n plus one on the next; a different function identity creates fresh callable state. RecursiveApply creates a RecursiveCall with current captures and a shared program. Each active recursion depth acquires a separate frame, fills different arguments, resets its evaluator, and evaluates. Frames return to the available pool as recursion unwinds. Normal and recursive function evaluation Normal Apply: call-site state continues logical tick n logical tick n + 1 F1(args) callable F1 state S(n) F1(args') same callable F1 state S(n + 1) callable state RecursiveApply: reset frame at each call depth RecursiveCall captures + shared program active call stack frame 0 args: n frame 1 args: n - 1 frame 2 base case available frame pool after unwind

Reading rule. The upper timeline follows one persistent call site across ticks. The lower layout separates simultaneously active recursion depths; returning a frame makes it reusable but does not merge states between depths.

State identity and replacement

Root transfer matches whole stream evaluators by semantic state keys. Call-site, branch, lifting, and delay state move only as part of a compatible evaluator owner. A changed stream program starts cold rather than attempting field-by-field similarity.

Nested expression replacement applies the same principle within an active dynamic operation. Immutable body programs may be cached, but mutable evaluator instances belong to activations and are not shared through that cache.

Resource behavior

EvaluatorState vectors are fixed by the bound graph. Delay rings are bounded by declared depth. Recursive frame storage grows to the maximum active recursion depth reached by that evaluator and reuses returned frames. Nested evaluator and route caches have separate lifetimes described in dynamic properties and execution tiers.

Implementation mapping

The implementation mapping is concentrated in src/dataflow/execution/evaluator_state.rs, src/dataflow/execution/functions.rs, src/dataflow/execution/node_evaluation.rs, and evaluator tests.