Commissioned for this museum · after Peano's axioms, by way of variadic tuple types
Arithmetic with No Numbers
TypeScript·2020·34 lines·1536 bytes
/**
* Arithmetic with no numbers in it, performed entirely by the type checker.
* Nothing below runs. All of it is computed before the program exists.
*/
/** A tuple of exactly N members: counting, done by building a thing to count. */
type Tally<N extends number, T extends unknown[] = []> =
T["length"] extends N ? T : Tally<N, [...T, unknown]>;
/** Addition. Lay two tallies end to end and ask how long the result is. */
type Add<A extends number, B extends number> =
[...Tally<A>, ...Tally<B>]["length"];
/** Subtraction. Take A apart until what remains is exactly B long. */
type Sub<A extends number, B extends number> =
Tally<A> extends [...infer Rest, ...Tally<B>] ? Rest["length"] : never;
/** Multiplication is repeated addition, so it is repeated concatenation. */
type Mul<A extends number, B extends number, Acc extends unknown[] = []> =
B extends 0 ? Acc["length"] : Mul<A, Sub<B, 1>, [...Acc, ...Tally<A>]>;
/** True only when the two types are exactly each other. */
type Is<X, Y> = X extends Y ? (Y extends X ? true : never) : never;
// Each of these is a proof. Break the arithmetic and the file stops compiling.
const three_and_four: Is<Add<3, 4>, 7> = true;
const ten_less_four: Is<Sub<10, 4>, 6> = true;
const six_sevens: Is<Mul<6, 7>, 42> = true;
// And one deliberate lie, to show the checker is awake rather than agreeable.
// @ts-expect-error three and four are not eight
const wrong_on_purpose: Is<Add<3, 4>, 8> = true;
export { three_and_four, ten_less_four, six_sevens, wrong_on_purpose };Curator’s note
Nothing in this file runs. Every sum in it is worked out before the program exists at all, by the type checker, as a side effect of deciding whether the file is well typed. Compile it and the answers are already known; run it and nothing happens.
It works because TypeScript will tell you the length of a tuple type, and will let you build a tuple by spreading other tuples into it. That is enough to count. Tally<3> is a tuple with three members in it, built by adding one at a time until the length matches — recursion in the type system, with the base case written as a condition. Once you can count, addition is laying two tallies end to end and asking how long the result is. Subtraction is pattern matching: take A apart and see what is left once B has been removed from the end. Multiplication is addition done B times.
None of this was designed. Conditional types were added so a library could say "this function returns whatever you passed it"; variadic tuples were added so that wrapping a function would not lose the shape of its arguments. Put together they turn out to be a small, slow, purely functional programming language that nobody can run on purpose.
The last line is a lie the checker refuses — 3 + 4 is not 8 — which is there because a proof that cannot fail is not a proof.
See also what a type system can and cannot prove.