Reactive Execution
How Arc executes programs using reactive dataflow and stratified scheduling
Why This Matters
Consider two functions reading the same pressure sensor:
tank_pressure -> safety_check{}
tank_pressure -> controller{} In traditional programming, if tank_pressure updates to 500 psi while your code is
running, safety_check might see 500 psi while controller sees the old value (400
psi). This inconsistency could cause subtle bugs or safety issues.
Arc prevents this. Both functions see exactly the same value. Either both see 400 psi or both see 500 psi, never a mix.
This is called stratified execution, and it’s why Arc programs are predictable and safe.
The Reactive Model
Arc programs don’t execute line-by-line like traditional code. Instead, they use a reactive dataflow model where computations run automatically in response to new data arriving on channels.
You declare how data flows through your system, and Arc handles the scheduling:
// When tank_pressure updates, scale it and write to display
tank_pressure -> scale{factor=2.0} -> pressure_display No loops. No polling. When tank_pressure receives new data, Arc automatically runs
scale and updates pressure_display.
Flow Statements and Edges
Flow statements connect channels and functions using edges. There are two types:
Continuous Edges (->)
Run every time data arrives:
// Scale sensor readings continuously
tank_pressure -> scale{factor=2.0} -> pressure_scaled
// Run a control loop every 50ms
interval{period=50ms} -> controller{}
// Average two sensors
(sensor_1 + sensor_2) / 2.0 -> average_display Every time the source produces data, the entire pipeline executes.
One-Shot Edges (=>)
Fire once when a condition becomes true, then stop:
// Abort when pressure exceeds limit (fires once)
tank_pressure > 600 => abort
// Advance to next stage when target reached
tank_pressure > 500 => next
// Log a message once when pressure drops
tank_pressure < 20 => log_message{} One-shot edges reset when the containing stage is re-entered.
A common mistake is using -> when you meant =>. If you want something to happen
once (like a stage transition), use =>. If you use ->, the transition will keep
firing every cycle while the condition is true.
Stratified Execution
Arc guarantees deterministic, glitch-free execution through stratification:
Snapshot consistency: All nodes in an execution cycle see the same values. If a channel updates while the graph is executing, every node sees the same snapshot.
Deterministic order: Nodes execute in a guaranteed order based on their dependencies. The compiler organizes nodes into “strata” (layers), where each stratum only executes after all nodes in the previous stratum have completed.
Without this guarantee, debugging becomes difficult and safety certification nearly impossible. You’d never know if a bug was caused by inconsistent data or actual logic errors.
No Loops
Arc doesn’t have for or while loops. This is intentional.
Instead of:
# Traditional approach (not Arc)
while True:
value = read_sensor()
if value > threshold:
trigger_alarm()
sleep(0.1) You write:
// Arc approach
sensor > threshold -> trigger_alarm{} The runtime handles the “loop”. Your code just declares what should happen when data arrives.
For computations that need to accumulate values over time, use stateful variables.
Expressions in Flows
Inline expressions work as implicit functions:
// Comparison
temperature > 100 -> alarm{}
// Arithmetic
(sensor_1 + sensor_2) / 2.0 -> display
// Logical combination
pressure > 100 or emergency -> shutdown{} These expressions can reference channels and literals, but not local variables from function bodies.
Cycle Detection
Flow graphs must be acyclic. You can’t create a loop where node A depends on node B which depends on node A. The compiler will reject this with an error.
The exception is stage transitions using =>. These can form cycles because they
represent valid state machine behavior:
sequence main {
stage idle {
start_btn => next
}
stage running {
stop_btn => idle // valid: can return to idle
}
}