Chapter 7. The Write Side

Definition 1.1 has a second component, and with it comes the strongest objection to everything so far. Documents can be declarative — nobody defends imperative newspapers. Applications, the objection runs, are different: they change things, they respond, and change is where declarative architectures fail. This chapter takes the objection at full strength and answers it in four propositions. Nothing from the read pipeline needs to be taken back; the write side is the factorization’s mirror image, and the smaller of the two.

The delta normal form

Start where Chapter 5 left the state: State = 𝒫(Fact), merge is union. What can change about a set? Elements leave; elements arrive. There is no third thing.

Prop. 7.1 (Delta normal form). Every state change factors as a pair of fact-sets — a delta:

write(r, S) = (S ∖ D⁻) ∪ D⁺                              (7.1)
D⁻ = S ∖ write(r, S)          the facts removed
D⁺ = write(r, S) ∖ S          the facts added
Proof — extensionality, then minimality.

With D⁻, D⁺ as defined, (S ∖ D⁻) ∪ D⁺ = write(r, S) by set extensionality. Minimality: any pair (A, B) with (S ∖ A) ∪ B = write(r, S) satisfies A ⊇ D⁻ (a fact of S absent from the result leaves only by removal) and B ⊇ D⁺ (a fact new in the result arrives only by addition). So (D⁻, D⁺) is contained in every such pair: it is the least pair, and a least element is unique. ∎

Two sets. That is the entire theory of mutation over a fact-set model. Measure that against what the industry maintains for mutation: object-relational mappers, undo stacks, reconciliation engines. Every one of these is machinery for computing or applying change over a model in which change has no normal form. Trees are the instructive case: two trees have no canonical difference, so deciding what “changed” is a heuristic. The industry’s clearest specimen is the virtual DOM’s diffing engine (Chapter 11): client-side frameworks re-compare an in-memory copy of the page on every render. That is a whole runtime spent recovering, approximately, what (7.1) gives exactly by subtraction. Sets subtract. The model that R2 forced for merging turns out to hand us mutation’s normal form as a by-product; union and difference are one algebra.

On the running example: the wind gusts, and the panel’s value moves. The delta is D⁻ = {(⟨…#panel-14⟩, value, "15.5 kW")} and D⁺ = {(⟨…#panel-14⟩, value, "16.1 kW")} — two one-element sets. That is the entire update, transport included.

Interactive exhibit (online edition): the gust, applied — edit the two sets and apply them against the live state. Apply the same delta twice and watch nothing happen: sets subtract, and re-application is a no-op.

And note what a delta is made of: fact-sets. Change is data in the same model as the state it changes — no second model, no change-description language with semantics of its own to invent. The delta is what fills the request’s Body from Chapter 1, and Chapter 8 will give the industry’s name for it.

Forms, run backwards

Prop. 7.2 (Forms are inverse transforms). The read pipeline ends at a human; the write side begins at one. The instrument is a form: a tree, part of a document, rendered by present like everything else. Its fields stand where a fact pattern’s variables stand. A form carries one pattern or several, each marked “remove” or “add”; submission binds their fields, and a bound pattern is a set of facts. The marked sets are (7.1)’s delta:

form   : Tree                fields ↔ variables of a fact pattern
submit : Bindings → (D⁻, D⁺)                             (7.2)

On the running example: the edit form arrives holding “15.5 kW”; the user types “16.1”; the stale binding, marked remove, is D⁻, and the fresh binding, marked add, is D⁺. One submission, both sets.

A form is arrange run backwards. The one factor that crossed graph→tree (Chapter 6) is also the one that must cross back, and it crosses on the same rails: patterns.

Two jobs meet in a form, and they must not be conflated. Construction — which fields an edit form should offer for an entity of this kind — is a projection of structure: read the patterns, render inputs. Validation — which deltas are admissible — is a predicate on (D⁻, D⁺). One reads structure; the other judges change. A schema drafted to do both jobs at once will do both badly — a line Chapter 19 builds on.

For the justification, view source on any HTML form since 1993. Field names are attribute names; method names the unsafe verb; the form is a fact pattern wearing input boxes. The web has shipped the inverse transform beside the forward one from the beginning.

One algebra, both directions

Prop. 7.3 (One algebra, both directions). Chapter 5’s selection algebra — match, join, union, project — is the write side’s algebra too. A pattern with free variables selects the facts that match; the same pattern with its variables bound denotes the facts of a delta. Selection finds what is; the delta says what shall be; the syntax between them is one syntax.

pattern + state      →  bindings                          (find)
pattern + bindings   →  (D⁻, D⁺)                          (change)

The consequence is practical as much as formal: the write side adds no expressive machinery. Whoever can query can update: an implementation gets its update language by handing its pattern matcher the bindings it would otherwise return. When Part III proves the read side complete, the write side inherits the result through this symmetry. Compare, once more, the industry’s arrangement: a query language, a separate mutation API, a migration DSL, a client-side state manager — four vocabularies for one algebra.

The five moves

Prop. 7.4 (Interactivity, decomposed). The objection’s strongest form: “real applications are interactive.” By independent evolution (Prop. 4.5) the document is doc(r, τ) = read_τ(r, S(τ)). That is a value with exactly five inputs: the request r, the state S(τ), and the three factor terms. So every interaction the web has ever shipped is one of exactly five moves:

  1. navigate — a new r: link, filter, page, search. The term unchanged; the argument different.
  2. writeS advances by a delta (7.1): submit, edit, delete.
  3. restyle — substitute the present term: the theme toggle.
  4. rearrange — substitute the arrange term: list to grid, sort, collapse.
  5. reselect — substitute the select term: a saved query edited, a dashboard reconfigured.

Moves 2–5 are independent evolution’s four timelines; move 1 is the request — an argument to the application, not a component of it (Chapter 4). There is no sixth move because there is no sixth input. Interactivity is the factorization, exercised. Fusion adds no move to this list; what it gains is the freedom to make the moves without saying which component they touch. Part IV will show what that freedom costs.

The latency concession

One honest concession remains, and it too should be met at full strength: latency. The operational complaint is the round trip — a keystroke should not cross an ocean to move a cursor. Granted. But look at what the complaint actually asks for: that the factors be evaluated near the user — not that they be fused. And mobility of evaluation is precisely what S2 already secured. A term whose semantics is closed evaluates the same everywhere. So ship q, t, s to the client and run them there, against a local replica of the selected data. The architecture has not changed by one proposition: the same terms, the same factors, a different machine. What cannot travel this way is a fused read: an opaque program can only be shipped whole and trusted blind. Shipping opaque programs to browsers is an experiment the web has already scored once (Chapter 12). Declarative terms are portable because they mean the same thing everywhere — S2, read as a deployment strategy.

The closed pipeline

The pipeline is now complete in both directions, and it closes:

flowchart LR
    S(["State"]) --> sel[["select"]] --> D(["Data"]) --> arr[["arrange"]] --> T(["Tree"]) --> pres[["present"]] --> Doc(["Doc"])
    Doc --> H(["reader"])
    H --> F(["form (7.2)"])
    F --> delta(["(D⁻, D⁺)"])
    delta -- "(S ∖ D⁻) ∪ D⁺" --> S

The closed pipeline. State becomes document by three factors; the document meets a human; the human’s answer is a delta; the delta is the next state — and τ ticks (4.5). Every arrow but the human’s is a numbered proposition. Everything before this figure derives it; everything after measures the world against it.

Change, the objection said, is where declarative architectures fail. Change is the arrow that closes the diagram.