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.
There are two locks. A read lock is one. It means the value is waiting. The value stays: the previous value, or the type's default when nothing has been stored. A calculation uses that value, and a read-locked input makes the result read-locked. A write lock is a counter. It means a reverse process is using the value.
A lock carries no error, no progress, and no retry. Those are application state. The program does not see a lock. A driver sees the boxed value and the lock. Setting a value clears the read lock. The lock is on the whole value and spreads to any structure or array built from it. A field has no lock of its own.
Two halves
The readable side
Holds the current value and one read lock. Locking for reading does not replace the value. A calculation that reads a locked value has a locked result. The program does not see the lock.
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.