Features · Sandboxed
Sandboxed
A Program never reaches out and does something directly. Every effect is emitted as plain data into a named channel, and a driver, the only code that touches the outside world, drains that channel and carries it out. Your program logic stays pure and sandboxed.
State in, emissions out
Seen from the outside, a Program is a pure function of one cycle. Its state goes in; nothing else does. What comes out is a set of context emissions — typed values dropped into named channels: a line for the console, a tree for the interface, a request for the network. The program itself touches nothing. Drivers, sitting at the edge, drain those channels and do the real work, and they are the only impure part of the system.
That boundary is the sandbox. Because only typed data crosses it — state in, emissions out — the logic can be run anywhere, against any set of drivers, with nothing of the outside world baked in.
Effects go through drivers
Writing a line, drawing the interface, making a request: each one is a value emitted into a channel, not an action taken on the spot. On every reactive cycle the runtime hands each driver the effects on its channel, and the driver does the real work. Swap the drivers and the same program runs in a terminal, a browser or a test harness, untouched.
STATE Text name = "world"
LET Text greeting = Text.Append "Hello, " name
Console.Line greeting
Console.Line does not print anything itself; it appends to the
Console.Lines channel, and the console driver prints it on the next cycle.
A very simple interface
Because effects are just data, a driver is tiny: a factory that takes a callback for feeding values back in, and returns a process function called once per cycle with that channel's emissions. A new capability is just a new driver, nothing more.
const ConsoleDriver = {
Driver: function (emit) {
return function process(lines) {
for (const line of lines) console.log(line);
};
}
};
Register it against a channel (here, Console.Lines) and every
Console.Line in the program flows to it. Output-only drivers ignore
emit; input drivers, such as a GPS receiver or a WebSocket, call it to push fresh
values back into the reactive graph.
Driving values back in
So far this is all one direction: state in, emissions out. The way the world gets back in is that an emission can carry a binding — a handle the driver may write to. When a driver has a result (a typed line, a clicked button, a response off the network) it writes it into that binding, and that write is the only way anything outside the program reaches back inside.
A binding is one of two things. It may be backed directly by state, and the write simply sets that state. Or it may be a reverse binding, and the write triggers a reverse tree: the change runs backwards through the calculations behind it and fans out onto the state at its leaves. Either way the next cycle re-runs the program with the new state, and fresh emissions go out.
This is the join between the two features. Sandboxing decides what may cross the boundary — typed emissions out, binding writes in — and reversibility decides where a write lands. A driver never touches state directly; it writes a binding, and the program works out the rest.
Snapshots
Because the logic is pure and nothing of the outside world lives inside it, the state of a running application is easy to snapshot at any point. The snapshot is just data: capture it, set it aside, and later restart the program from exactly that state. The drivers reconnect, read what the program now wants done, and carry on from where it left off, as if it had never stopped.
That one capability covers a lot of ground:
- Replays.
Snapshot at each step and you can step a run backwards and forwards, or re-run it later to see exactly how it reached a given state.
- Transplants.
A snapshot is portable, so a running program can be lifted off one machine and resumed on another, picking up mid-flight with fresh drivers attached.
- Thin clients.
Because the interface is itself an effect, it need not be rendered where the program runs: the UI can be sent across to another machine to be drawn there, with the interactions sent back into the reactive graph, while the program keeps running elsewhere.