Internals · Running
Running
A running Program is a loop. Each turn of the loop is an update cycle: the current state and any pending updates go in; the new state and a set of effects come out; the effects reach the outside world through drivers, and the drivers feed the next round of updates back to the top. The loop turns until there is nothing left pending, then rests until something outside stirs it again.
The cycle
One cycle, end to end:
- In: state + updates. The cycle begins with the program's current state and a set of value updates — a map from state cell to new value. On the very first cycle that set is empty; on later cycles it holds whatever the drivers fed back.
- Run. The updates are applied to their cells and the run body is re-evaluated. Only what depends on a changed cell recomputes; everything else is left as it was.
- Out: effects + new state. The cycle yields the next state and the effects the program emitted this turn — lines to print, requests to send, frames to schedule, UI to render — each queued on its own channel.
- Drivers. Each channel's effects are handed to the driver that owns it. The driver is what actually touches the world.
- Back to the top. A driver may produce a result — a response body, a timer tick, a socket message — and hand it back as updates. Those updates become the input to the next cycle.
In the reference interpreter the run step is a single call, the program value threaded through it from one cycle to the next:
// pending updates gathered since the last cycle (empty on cycle one)
program = Reactive.Run(program, hasUpdates ? valueUpdates : null);
// drain each channel and hand its effects to the owning driver
driver.process(emissions);
Effects and drivers
The program never reaches outside itself. Instead it emits onto channels, and a driver drains each channel and performs the real work: the Console driver writes lines to output, the Network driver makes HTTP requests, an animation driver schedules frames, a signal driver carries WebSocket messages. Which drivers exist is fixed by a driver spec; a program that emits to a channel whose driver was not enabled simply has those effects go nowhere (or, for a required channel, stops with a “driver is not present” error).
Drivers are also the only way new input enters. When a driver has something to report it calls back with a set of updates. That is the sole path from the outside world into the program's state, which keeps the run body pure: it reads state and emits effects, and never performs I/O directly.
Settling and rest
If a driver hands back updates, the loop schedules another cycle to apply them; that cycle may emit more effects, which may produce more updates, and so on. The loop keeps turning until a cycle leaves nothing pending — then it is quiet. A one-shot program (print and exit) settles after a cycle or two and the process ends. A long-lived one comes to rest and waits: an asynchronous driver — a timer, a fetch resolving, a socket message — eventually fires, feeds updates back in, and starts the loop turning again for the next cycle.