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

gettable

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.

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.Calculateyesforward: read-only derived value
Reactive.Reverseyesreverse: 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.