Control

Logic.If and Collection.Array.Each are holes. A driver implements them. The runtime keeps the child scope, so the next run finds the same state. The program calls them like any other function.

Logic.If(signedIn, () => {
  Content.Text(name);
}, () => {
  Content.Text("Sign in");
});
One arm runs. The condition is a Logic.Maybe.

If

Logic.If takes the condition, then the arm for true, then the arm for false. The active arm runs. The other arm does not. A read lock on the condition takes neither arm. The probe records the gathers those arms would have emitted into, and those gathers are read-locked.

Logic.If(signedIn, () => {
  const visits = Reactive.State(Maths.Integer.Number)(0);
  Content.Text(visits);
}, () => {
  Content.Text("Sign in");
});
The true arm runs. A later run keeps visits while the condition stays true.

Each

Collection.Array.Each takes the array, then a block. The block's input is one element. The driver supplies that input. Each element has a stable id, and the next run keeps that element's scope by that id. A read lock on the array takes no element.

const list = Collection.Array.Of(People.Person)([
  People.Person({ name: "Ada Lovelace", age: 36 })
]);
const people = Reactive.State(Collection.Array.Of(People.Person))(list);

Collection.Array.Each(people, () => {
  const person = Reactive.Input(People.Person);
  Content.Text(person.name);
});
The array is built from a plain JavaScript array. The block runs once for each person.

What the driver calls

The If driver reads the condition. It calls Reactive.Runtime.Enter with the arm it takes, and Reactive.Runtime.Absent for the other. A locked condition calls Reactive.Runtime.Probe on each arm and enters neither. The Each driver reads the array and calls Reactive.Runtime.Each with the element ids and the block. The runtime matches the scopes. The driver does not store them.