Reactive Framework Architectural Decisions

A set of architectural decisions affects how we make the reactive framework. These decisions also affect its capabilities.

Update Propagation

A key decision is how to deal with updating the program when values change.

Approach 1 · Re-execute the program

On every change, run the whole program again and reuse the parts that cannot have moved. One code path both builds the program and updates it. The state stays in step with the program's shape for free.

ProsCons
  • The simplest data structures and the simplest code.
  • One path builds and updates, so there is little to keep in sync.
  • State follows the program's shape automatically.
  • A full re-run reaches wider than the change did.
  • Staying fast still means tracking dependencies, to know what to reuse.
  • Matching the new output against the old adds a reconcile pass.

Approach 2 · A dependency graph chosen

Record every dependency as a graph: from each value to the calculations that read it, and from each calculation to the values it reads. A change walks only the dependents and recomputes them in order. The work matches what moved.

ProsCons
  • Work is proportional to the change, so a large program stays cheap to update.
  • The whole apparatus is a dependency graph and an ordered walk over it.
  • It runs the program directly, in one pass.
  • The graph has to be maintained as the program grows and shrinks.
  • A change of shape, a block appearing, needs its own handling.
  • The update order has to be correct, so every input is settled first.

Approach 3 · Capture, then interpret

One initial pass builds a data structure describing the whole program, and execution then works over that structure. Description and running stay cleanly separate, and the captured program is portable and inspectable.

ProsCons
  • Description and execution stay cleanly separate.
  • The captured program is portable and can be inspected.
  • One structure can be run, checked, or compiled.
  • It adds an interpretation layer over the captured structure.
  • It needs a build pass before anything runs.
  • More moving parts than running the program directly.

The decision

Reactive takes the second. It keeps a directed graph of every dependency in the program. When a value is set, it walks that graph and recomputes only the nodes downstream of the change, in order. Every input is settled before the node that reads it runs. The order comes for free. Nodes are grouped into scopes, and a node's inputs are built before it, so index order within a scope is dependency order. Scopes nest into a tree the walk descends. There is no separate sort and no heap. Each node recomputes once per change, always on final inputs.

A program also changes shape: a block appears, a list grows. For that, Reactive re-executes the section that changed. An If whose condition turns true runs its block for the first time. An Each runs its callback on each new element. This section-level re-execution sits alongside the value-level recompute as a second, complementary kind of update.

ChangeWhat Reactive does
A value is setRecompute its dependents in order, down the scope tree.
A section appears or disappearsRe-execute that section's builder, once, for the part that changed shape.

Next Steps

We have the model: a dependency graph, recomputed in order down a tree of scopes, with section-level re-execution for shape changes. Next, Root starts to build it. Root is the container that holds the program as data. From there we add values, the dependency graph, then the rest.