Reverse

Reactive.Reverse takes the type, then the source and the block. Reading the reverse reads the source. Writing it runs the block. The callback argument is the value just set. Reactive.Set in the block stores that write on a state.

Celsius in state, Fahrenheit on the input

The state holds Celsius. The forward calculation turns that into Fahrenheit, and that result is the source of the reverse. The Fahrenheit input shows the source. Typing in it runs the reverse, which stores Celsius. The Celsius field writes the state directly, so editing either side updates the other.

const celsius = Reactive.State(Maths.Real)(20);

// convert Celsius to Fahrenheit
const scaled = Maths.Real.Multiply(celsius, 9);
const ratio = Maths.Real.Divide(scaled, 5);
const fahrenheit = Maths.Real.Add(ratio, 32);

// convert Fahrenheit back to Celsius on change
const fahrenheitReverse = Reactive.Reverse(Maths.Real)(fahrenheit, (written) => {
  const adjusted = Maths.Real.Subtract(written, 32);
  const scaledBack = Maths.Real.Multiply(adjusted, 5);
  const next = Maths.Real.Divide(scaledBack, 9);
  Reactive.Set(celsius, next);
});

Layout.Row(() => {
  Input.Number(celsius);
  Content.Text(" °C");
  Input.Number(fahrenheitReverse);
  Content.Text(" °F");
});
Reading fahrenheitReverse reads fahrenheit. Writing it sets celsius. Forward is Celsius × 9 ÷ 5 + 32. The block subtracts 32, multiplies by 5, and divides by 9.

The two directions

The three lines before the reverse are the forward direction. They run whenever the program runs, and fahrenheit is the Fahrenheit the field shows. The block is the reverse direction. written is the Fahrenheit just typed. The block builds the Celsius for that Fahrenheit and sets the state. The next run computes fahrenheit again from the stored Celsius, and the field shows it.

The Fahrenheit field is the same Input.Number as a field bound to state. It reads a value and writes a value. Here the value is the reverse, so a read shows Fahrenheit and a write runs the block.