Cobham's algebra — the working toolkit #
Derived members of Complexity.Cobham: the operations a Turing-machine
interpreter written inside the algebra needs. Each is a single limited recursion
on notation, or a finite composition of such.
Two of these carry the weight. dispatch shows that branching is free: the step
functions of recNotation are already selected by the bit being peeled, so a
one-step recursion on v 0 is an if-then-else on its leading bit.
dropPrefix shows how to move an argument that changes along a recursion —
recNotation fixes its parameters, so the changing value has to live in the
recursion's value, and iterating tail there gives drop. With drop in
hand, takePrefix reads off successive bits, and fixed-width pairing with
projections follows.
Composition with two inner functions, packaged for readability: the
constructor's Fin-indexed family is awkward to supply when the two components
differ.
The self-delimiting pairing pair x y = delimit x ++ y is in the class, by
limited recursion on notation on x: each peeled bit is doubled onto the
recursive value, and the base case emits the separator 01 followed by y. The
bound is exact — |pair x y| = |x ++ x| + |y ++ [0,1]|.
Bit dispatch is in the class. caseBit (v 0) (v 1) (v 2) is a single
limited recursion on notation over v 0: the recursion's own bit-selected step
functions do the branching, projecting out v 1 or v 2, and the concatenation
of the two branches bounds the result.
Dropping a prefix of a given length is in the class. v 1 is advanced by
one tail per bit of the ruler v 0: the recursion applies tail to its own
recursive value, so the changing argument lives in the recursion's value rather
than in its parameters — which is what makes it expressible at all.
Taking a prefix of a given length is in the class. Each bit of the ruler
v 0 appends one more bit of v 1, read off by dispatching on the head of what
is still undropped — so dropPrefix and dispatch together give take.
Total bit dispatch is in the class. Same recursion as dispatch, except
the base case returns the false branch instead of the empty string — so the
empty string reads as false and every flag is genuinely one bit.
The nonemptiness flag is in the class. This is the one consumer of the
partial dispatcher: both branches are [true], so the flag is [true] exactly
when there is a bit to read and [] otherwise.
Matching against a fixed constant is in the class. For each constant the
test unfolds into finitely many bit comparisons joined by andFn, so this is a
finite composition — the meta-level induction is on the constant, not a
recursion inside the algebra.
A block of |x| zeros is in the class, by limited recursion on notation:
each peeled bit prepends one 0 to the recursive value, and the argument bounds
the result.
Block addressing is in the class. With every field of a configuration
padded to the ruler's width, field i is takeFn after dropping i rulers —
and i is a fixed natural number, so the drop is a finite concatenation.
Fixed-width padding is in the class. With every field of a simulated
configuration padded to one ruler's width, field i is recovered by dropping i
rulers and taking one — so no self-delimiting decoder is ever needed inside the
algebra.
Finite table dispatch is in the class. Matching a member of the class
against each of finitely many constant patterns in turn, taking the first
branch that fires and a default otherwise, is a finite chain of iteFns.
This is exactly the shape of a Turing machine's transition function: the patterns are the (state, symbols-read) combinations, of which there are finitely many for a fixed machine, and the branches assemble the successor configuration.
A table of constant patterns is a case analysis. If some entry's pattern prefixes the key, and every entry whose pattern prefixes the key carries the same value, then the fold returns that value — regardless of the order the entries appear in.
Phrasing it as "all matching entries agree" rather than "exactly one matches" avoids having to prove the patterns pairwise distinct: for a transition table the patterns are distinct, but agreement is the weaker and more convenient obligation.
Clocked iteration #
The engine of the completeness direction: a machine is simulated by iterating its one-step transition function a polynomial number of times, and both halves of that — the iteration and the polynomial clock — are cheap inside the algebra.
Bounded iteration is in the class. Iterating a step function once per bit
of a clock string is a single limited recursion on notation: the recursion
ignores which bit it peels and simply applies the step to its own recursive
value, so h₀ = h₁ = f ∘ Fin.tail. The clock's length is the iteration count,
which is why polynomial clocks (exists_pow_clock) give polynomially many
steps.
Clocks. For every constant c and exponent d there is a member of the
class whose value on v is at least c · (|v 0| + 1) ^ d bits long — built from
constants and smash, which is exactly what smash is for.