Values
Everything the reactive core works with is a reactive value, and every reactive value is built from two optional halves: a gettable, its readable side, and a settable, its writable side. Which halves are present decides whether a value can be read, written, or both. That one structure lets forward calculations, reverse functions and two-way bindings all be the same kind of thing.
Everything is a reactive value
Assume that everything handed to you is a reactive value.
That rules out direct arithmetic: celsius * 9 is meaningless, because celsius is a
wrapped value rather than a number.
A raw value appears inside a calculation, where
Reactive.Value(x) unwraps a reactive input so you can compute with it and return a raw result.
Everywhere else, values stay wrapped. Constants are the exception you may always pass: a function like
Maths.Real.Multiply(celsius, 9) takes a reactive value and a plain 9 side by
side, so raw and reactive mix freely in the code, and you should treat any value you hold as reactive.
Ready, or not yet
Values are always wrapped for a reason: each one carries whether it is ready. A reactive value may hold a value, or be locked: present but pending, the way a result fetched over the network arrives after the first moment. The wrapper records that readiness, so a value can honestly report “pending” in place of a missing or stale number.
A lock comes in two shapes. Most of the time it means loading: the value is on its way. It can also carry an error: a message saying what went wrong, and an optional retry flag you can set to try again. Failure travels exactly like a pending value, as a locked value, so it reuses the same channel; the two are the same mechanism.
You rarely deal with any of this by hand, because readiness propagates through calculations on its own: anything derived from a pending or errored value is itself pending, and carries the reason with it. That is how Reactive unifies synchronous and asynchronous data: it is all just values that are either ready or pending. The exact shape of the wrapper, gettable, settable, lock and error, is set out in the Reactive module reference.
The way back travels with the failure, too. An error's retry flag is a settable, and because errors flow forward through calculations, so do their retry flags. A value derived from several failing inputs gathers their flags into a single combined retry: set it, and every source that broke re-runs at once. Recovery is as automatic as the error: one retry, assembled for you, reaches all the way back to whatever actually failed.
Two halves
The readable side
Holds the current value, read with .value. It also carries a lock: a value can be
present, locked while an async result is pending, or failed, carrying an error message and a retry flag.
A lock propagates through any calculation that reads the value, so “pending” and
“failed” travel forward on their own.
The writable side
Holds a stable identity and an optional reverse function. Reactive.Set targets the
settable: if it carries a reverse, that function runs; otherwise the value is a source and the new value is
stored against its identity.
Reading only ever touches the gettable; writing only ever touches the settable. A value with just one half
supports only that direction: a read-only value offers a gettable to read, and a write-only value offers a
settable to Set.
Forward, reverse, or both
The primitives differ only in which halves they produce.
// both — a source you read and write
const count = Reactive.State(Maths.Integer)(0);
// gettable only — a read-only derived value
const label = Reactive.Calculate(() => `${count.value}`);
// settable only — a write-only action
const clear = Reactive.Reverse(() => Reactive.Set(count, 0));
| Primitive | Gettable | Settable | Direction |
|---|---|---|---|
Reactive.State | yes | yes | both: read and write a source |
Reactive.Calculate | yes | — | forward: read-only derived value |
Reactive.Reverse | — | yes | reverse: write-only action |
Reactive.Binding | yes | yes | both: a gettable joined to a settable |
A value can be forward, reverse, or both, and Reactive.Binding makes the “both” case
by joining a forward gettable to a separate reverse settable. That is exactly how a two-way value like the
Fahrenheit field on the reversibility page is put together.
Why split it
Because a forward calculation and its way back are independent,
keeping the halves separate lets you assemble exactly the value you need: a read-only reading, a write-only
action, or a two-way binding built from one of each. The rest of the framework treats them uniformly.
Reactive.Set looks for a settable; a read looks for a gettable. Uniform structure,
composable pieces.