Arithmetic by HTTP Redirect, Proved to Halt
A redirect is a tail call: the server hands the client the next URL and the
client jumps to it. Chain enough of them and you have a (very slow) computer.
Here is a Peano calculator in nothing but Apache 301s — succ and pred as
unary functions in a URL — and a TLA+ proof that every expression halts.
1. The calculator
Numbers are unary tally marks: | is 1, || is 2, ||| is 3, and the
empty string is 0. No decimal, no integers — just tally pipes in a URL.
This is Church-style: the numeral is the iteration count. A redirect is
a function application.
Two operations, composed left-to-right in the path:
- =/math/succ/TALLY= → 301 → =/math/TALLY|= (append one pipe: +1)
- =/math/pred/|TALLY= → 301 → =/math/TALLY= (remove one pipe: -1)
- =/math/pred/= → 301 → =/math/= (pred 0 = 0, monus)
- =/math/TALLY= (no function left) → 302 → research note. Done.
Compose them and the URL is a lambda-like expression. Each 301 applies the innermost function (the one nearest the tally) and drops that segment:
/math/succ/pred/||| → 301 /math/succ/|| (pred ||| = ||) → 301 /math/||| (succ || = |||) → 302 (done, result = 3)
Try it live: succ(succ(succ(0))) = 3 or succ(succ(pred(3))) = 4.
succ is the successor constructor (Lean4 Nat.succ, Racket add1);
pred is the predecessor (Lean4 Nat.pred, Racket sub1) with monus
semantics (pred 0 = 0, not an error).
Why it halts: every hop consumes a function segment, so the path strictly
shrinks toward the bare tally. An all-succ expression halts just as surely
as an all-pred one — the value grows but the path empties. Hops = the
number of function segments. Apache cannot do arithmetic, so the representation
must be unary — succ appends a character and pred strips one. Pure
string manipulation, no math.
2. The spec
---------------------------- MODULE MathRedirects ----------------------------
\* /math/f1/f2/.../fk/n with each fi in {succ, pred}. Each 301 applies the
\* innermost function and drops it, so the path shrinks one segment per hop.
EXTENDS Naturals, Sequences
CONSTANTS MaxLen, MaxBase
Fns == {"succ", "pred"}
Apply(f, m) == IF f = "succ" THEN m + 1
ELSE IF m = 0 THEN 0 ELSE m - 1 \* pred = monus (0 - 1 = 0)
VARIABLES funcs, value, phase, hops
vars == <<funcs, value, phase, hops>>
BoundedSeqs == UNION { [1..k -> Fns] : k \in 0..MaxLen }
Init == funcs \in BoundedSeqs /\ value \in 0..MaxBase
/\ phase = "eval" /\ hops = 0
Step == /\ phase = "eval" /\ Len(funcs) > 0
/\ value' = Apply(funcs[Len(funcs)], value) \* apply innermost
/\ funcs' = SubSeq(funcs, 1, Len(funcs) - 1) \* drop it: path shrinks
/\ hops' = hops + 1 /\ phase' = "eval"
Done == /\ phase = "eval" /\ Len(funcs) = 0
/\ phase' = "done" /\ UNCHANGED <<funcs, value, hops>>
Next == Step \/ Done
Spec == Init /\ [][Next]_vars /\ WF_vars(Next)
Terminates == <>(phase = "done") \* every expression evaluates
HopsBounded == hops <= MaxLen \* hops = number of functions
=============================================================================
Config: SPECIFICATION Spec, CONSTANTS MaxLen = 4 MaxBase = 5,
INVARIANT HopsBounded, PROPERTY Terminates.
3. The result
Tangle the block to MathRedirects.tla in a temp dir and run TLC (with
deadlock-checking off — the terminal /math/n states have no successor by
design):
Model checking completed. No error has been found. 570 states generated, 424 distinct states found
Every expression over {succ, pred} up to length 4 on a base <= 5 — 424
reachable states — evaluates to a numeral and stops. hops equals the function
count: bounded recursion, proven.
4. The catch
What does not halt is a rule that grows the path instead of consuming it.
A redirect like /math/succ/n -> /math/succ/succ/n — lengthening, not applying
— never reaches a numeral; it is the deliberate non-terminating canary in the
redirect-termination model, the
ouroboros, and it ends in ERR_TOO_MANY_REDIRECTS. The distinction is
well-foundedness: consuming a segment shrinks toward 0; growing one does not.
succ-the-function halts; succ-the-path-rewrite does not.
Useful? No. But it is a machine-checked Peano calculator assembled entirely from
.htaccess 301s — which is more than most .htaccess files can claim.