UI for sums must remember products
Sun 21 Jul 2024 09:16 EDT
I had a small insight yesterday while building a component for a small web app: the user interface for editing an incomplete value of sum type A+B needs to remember a product of input 2×A×B from the user:
A + B ⟿ 2 × A × B
This allows the user to ergonomically change their mind about whether they’re building an A or a B without losing partially constructed values.
More precisely, the UI for a value of type A+B needs in general to be able to remember and manipulate 2×(A+1)×(B+1):
A + B ⟿ 2 × (A+1) × (B+1)
The extra 1s allow for nulls, for temporarily missing but required values. You could similarly generalise to allow for temporarily invalid or unparseable values.
Example
Consider UI for creating a new project in an IDE, with two available options: create a new local project, by simply creating a new directory, or clone an existing git repository.
data NewProject =
Local { projectName :: String }
| Clone { gitUrl :: String,
credential :: String,
projectName :: String }
Abstractly, this is roughly Str + Str×Str×Str.
The user interface for this will look something like
Here we see that while a value of type NewProject
is being built, we need
to remember four strings (abstractly, Str×Str×Str×Str),
plus a boolean indicating whether we ultimately want a “local” or “clone”
project type (abstractly, 2).
All told, that’s
Str + Str×Str×Str ⟿ 2 × Str×Str×Str×Str
which exactly fits the pattern of
A + B ⟿ 2 × A × B
Generalization to bigger sums
The translation can be applied recursively, but it (harmlessly) remembers slightly too much transient UI state,
A+(B+C) ⟿ 2 × A × (2 × B × C)
so perhaps it’s better to think about it applying directly to n-ary sums:
A+B+C ⟿ 3 × A × B × C
A+B+C+D ⟿ 4 × A × B × C × D
and so on.