Let's build a reactive system from scratch. Not a UI framework like React, which can only ever output an interface, but one consistent mechanism for driving anything, interface, audio, networking, animation, or something you invent yourself.
In broad terms, a reactive program is one that holds some state and updates itself automatically when that state changes. Simple enough. But it raises an awkward question: what is state, and where does it live?
State has to live anywhere
For a system to be genuinely modular and composable, state has to be able to live anywhere. If I call a function from some place in the code, it might need to keep state of its own; think of a character in a game with its own position, health and little decision-making process, or a tabset that should simply remember which tab is open. I want to drop these in and have each keep its own state, without me finding somewhere to put those values by hand.
So when a function is called, it needs to know which instance of that call it is. The whole trick is keeping track of that, so that when the program re-runs we know we are calling the same function in the same place, and can hand it back the same state.
Let the code be the tree
The usual answer is a tree of objects, each holding its own state and a list of children. It works, but those objects have to be managed by hand, created when they appear, cleaned up when they go, and then, to render or process anything, you walk the tree calling update and render functions, and wire up event handling carefully along the way. In a complex interface that wiring becomes the bulk of the code.
But here is the counter-intuitive bit. What if the structure of the code itself is the tree? Functions instead of objects; calling the root function instead of walking the tree from the root by hand. The shape of your calls already forms a tree, so the runtime can use that as the identity of each instance, and hang state off it automatically.
Once the call structure is the tree, an enormous amount of bookkeeping simply disappears: no manual lifecycle, no walking, and, as we will see in later posts, no event-handler wiring either. That is the key the rest of the language turns on.