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

gettable

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.

settable

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));
Primitives producing each combination of halves
PrimitiveGettableSettableDirection
Reactive.Stateyesyesboth: read and write a source
Reactive.Calculateyes—forward: read-only derived value
Reactive.Reverse—yesreverse: write-only action
Reactive.Bindingyesyesboth: 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.