An Abuse of Notation
t ∈ ⟦Swift⟧
A guided tour through encoding natural numbers and mathematical proofs as Swift types. Every statement is verified by the compiler — if this program compiles, every theorem is correct. The source code for this project is available on GitHub
1. Peano types
The natural numbers are built from two primitives following the Peano axioms:
Zero— the number 0AddOne<N>— the successor of N, written S(N) in mathematical notation (i.e. N + 1)
Throughout this tutorial, S(N) denotes the successor function: S(0) = 1, S(1) = 2, S(2) = 3, and so on. In Swift, S(N) is the type AddOne<N>.
Every natural number is a unique nesting of AddOne around Zero:
// 0 = Zero
// 1 = AddOne<Zero>
// 2 = AddOne<AddOne<Zero>>
// 3 = AddOne<AddOne<AddOne<Zero>>>
// Type aliases N0 through N9 provide shorthand:
assertEqual(N0.self, Zero.self)
assertEqual(N1.self, AddOne<Zero>.self)
assertEqual(N2.self, AddOne<AddOne<Zero>>.self)
assertEqual(N3.self, AddOne<AddOne<AddOne<Zero>>>.self)
Peano construction: each natural number is a unique nesting of AddOne around Zero.
The assertEqual function has an empty body. Its signature requires both arguments to have the same type:
func assertEqual<T: Integer>(_: T.Type, _: T.Type) {}
If the two types differ, the compiler rejects the call. If they match, it compiles — and that successful compilation is the assertion. This is the Curry–Howard correspondence in action: types are propositions, and a compiling program is a proof.
How assertEqual works
The function assertEqual<T: Integer>(_: T.Type, _: T.Type) requires both arguments to unify to the same generic type T. The body is empty — the type checker does all the work. If the types match, compilation succeeds. If not, compile error. There is no runtime cost.
The Integer protocol hierarchy. Zero sits at the intersection of Natural and Nonpositive. Solid lines are unconditional conformances; dashed lines are conditional (requiring a where clause on the type parameter).
2. Addition witnesses
A witness is a type whose existence proves a mathematical fact. The NaturalSum protocol declares three associated types — Left, Right, and Total — representing the operands and result of an addition. Each conforming type provides concrete type aliases for them:
PlusZero<N>— a witness that N + 0 = N. It setsLeft = N,Right = Zero,Total = N.PlusSucc<P>— given a witness P that A + B = C, a witness that A + S(B) = S(C). It setsLeft = P.Left,Right = AddOne<P.Right>,Total = AddOne<P.Total>.
When you write PlusZero<N5>.Total, Swift resolves the type alias: Total = N, and N is N5, so .Total is N5. For PlusSucc<PlusZero<N5>>.Total, it resolves Total = AddOne<P.Total>, where P is PlusZero<N5>, so P.Total is N5 and .Total is AddOne<N5> = N6. Every associated type in this project — .Shifted, .Commuted, .Distributed, and the rest — works the same way: it's a type alias that the compiler resolves by following the chain of definitions.
// Theorem: 0 + 0 = 0
assertEqual(PlusZero<N0>.Total.self, N0.self)
// Theorem: 2 + 3 = 5
typealias TwoPlusThree = PlusSucc<PlusSucc<PlusSucc<PlusZero<N2>>>>
assertEqual(TwoPlusThree.Total.self, N5.self)
// Theorem: 3 + 2 = 5 (commutativity instance)
typealias ThreePlusTwo = PlusSucc<PlusSucc<PlusZero<N3>>>
assertEqual(TwoPlusThree.Total.self, ThreePlusTwo.Total.self)
Witness chain for 2 + 3 = 5. Each PlusSucc wrapping increments Right and Total by one.
Why witnesses, not computation?
A runtime approach would compute and check. The witness approach constructs a proof object whose existence guarantees the result. This is mathematical induction encoded in the type system.
3. Continued fractions and pi
A continued fraction (CF) is a nested expression of the form a0 + 1/(a1 + 1/(a2 + ...)). The integers a0, a1, a2, ... are called coefficients. The shorthand [a0; a1, a2, ...] lists them in order — for example, [1; 2, 2, ...] means 1 + 1/(2 + 1/(2 + ...)). Truncating the CF at depth n gives a rational approximation called the n-th convergent. A partial sum is the analogous concept for a series: the sum of its first n terms.
Two classical formulas approximate pi: Brouncker's CF for 4/pi and the Leibniz series for pi/4. At each depth, the CF convergent equals the reciprocal of a Leibniz partial sum. Throughout this project, .P denotes the numerator and .Q the denominator of a type-level fraction.
In section 2, we built witness chains by hand: PlusSucc<PlusSucc<PlusZero<N2>>> for 2 + 3 = 5. For simple arithmetic this is manageable, but proving that a convergent equals a partial sum requires witness chains dozens of types deep, with exact numerical values that must be computed first. We need something to do that computation and emit the right types.
A Swift macro is a compiler plugin that generates code at compile time. In this project, macros perform arbitrary integer arithmetic — the kind of computation the type system can't do — and emit witness type chains that the type system can verify. This division of labor is the key idea: the macro is the proof search (finding the right witness chain), and the type checker is the proof verifier (confirming that the chain satisfies every protocol constraint). The search is unchecked computation; the verification is structural and cannot be fooled.
The macro does arbitrary computation at compile time. The type checker structurally verifies each chain.
@PiConvergenceProof(depth: 3)
enum PiConvergenceProof {}
assertEqual(PiConvergenceProof.Convergent0.P.self, N1.self) // h_0 = 1
assertEqual(PiConvergenceProof.Convergent1.P.self, N3.self) // h_1 = 3
assertEqual(PiConvergenceProof.Convergent2.P.self, N15.self) // h_2 = 15
assertEqual(PiConvergenceProof.Convergent3.P.self, N105.self) // h_3 = 105
assertEqual(PiConvergenceProof.LeibnizSum2.P.self, N2.self) // S_2 = 2/3
assertEqual(PiConvergenceProof.LeibnizSum3.P.self, N13.self) // S_3 = 13/15
Macro expansion (depth: 1)
@PiConvergenceProof(depth: 1)
enum PiConvergenceProof {}
enum PiConvergenceProof {
typealias Convergent0 = GCFConvergent0<N1>
typealias ConvergentSumH1 = PlusSucc<PlusZero<N2>>
typealias ConvergentSumK1 = PlusZero<N2>
typealias Convergent1 = GCFConvergentStep<Convergent0, ...>
typealias LeibnizSum1 = LeibnizBase
typealias LeibnizWitness2 = PlusSucc<PlusZero<N2>>
typealias LeibnizSum2 = LeibnizSub<LeibnizSum1, ...>
func piCorrespondenceCheck() {
assertEqual(Convergent1.P.self, LeibnizSum2.Q.self)
assertEqual(Convergent1.Q.self, LeibnizSum2.P.self)
}
}
4. Wallis product
The Wallis product for pi/2. Each step multiplies the numerator by (2k)² and the denominator by (2k-1)(2k+1). The structural fingerprint: the numerator exceeds the denominator by 1 at each step — the difference-of-squares identity (2k)² = (2k-1)(2k+1) + 1.
WallisStep advances the partial product by one step. Its type parameters are multiplication witnesses for the four factors involved: the numerator is multiplied by 2k twice (squaring), while the denominator is multiplied by 2k-1 then 2k+1. The macro supplies these witnesses using two universal theorems that are formally proved in section 10:
N.OneTimesProof— a witness that 1 * N = N, available on every natural number..Distributed— given a proof that a * b = c, produces a proof that (a+1) * b = c + b. Chaining.Distributedrepeatedly builds up the left operand:N2.OneTimesProofwitnesses 1 * 2 = 2, then.Distributedproduces 2 * 2 = 4, another.Distributedgives 3 * 2 = 6, and so on.
@WallisProductProof(depth: 2)
enum WallisProductProof {}
assertEqual(WallisProductProof.Wallis0.P.self, N1.self) // W_0 = 1/1
assertEqual(WallisProductProof.Wallis1.P.self, N4.self) // W_1 = 4/3
assertEqual(WallisProductProof.Wallis1.Q.self, N3.self)
Macro expansion (depth: 1)
@WallisProductProof(depth: 1)
enum WallisProductProof {}
enum WallisProductProof {
typealias Wallis0 = WallisBase
typealias Wallis1 = WallisStep<Wallis0,
N2.OneTimesProof,
N2.OneTimesProof.Distributed,
N1.OneTimesProof,
N3.OneTimesProof>
typealias WallisFactor1 =
PlusSucc<PlusZero<N3>>
func wallisFactorCheck() {
assertEqual(WallisFactor1.Total.self, N4.self)
}
}
5. Fibonacci at the type level
The Fibonacci sequence encoded as type-level state transitions. Each state has three associated types: .Prev, .Current, and .Next. These are type aliases, resolved the same way as .Total (section 2). Fibonacci0 sets them directly: Prev = N1, Current = Zero, Next = N1.
FibonacciStep<S, W> advances the state by one position. It takes a previous state S and an addition witness W proving that S.Current + S.Next = W.Total — the Fibonacci recurrence. Its type aliases shift forward: Prev = S.Current, Current = S.Next, Next = W.Total. So each .Current resolves through the chain: FibonacciStep<S, W>.Current is S.Next, which is the previous step's W.Total.
@FibonacciProof(upTo: 10)
enum FibonacciProof {}
assertEqual(Fibonacci0.Current.self, N0.self) // F(0) = 0
assertEqual(FibonacciProof.Fibonacci1.Current.self, N1.self) // F(1) = 1
assertEqual(FibonacciProof.Fibonacci3.Current.self, N2.self) // F(3) = 2
assertEqual(FibonacciProof.Fibonacci5.Current.self, N5.self) // F(5) = 5
assertEqual(FibonacciProof.Fibonacci6.Current.self, N8.self) // F(6) = 8
Macro expansion (upTo: 3)
@FibonacciProof(upTo: 3)
enum FibonacciProof {}
enum FibonacciProof {
typealias FibonacciWitness1 = PlusSucc<PlusZero<Zero>>
typealias Fibonacci1 = FibonacciStep<Fibonacci0, FibonacciWitness1>
typealias FibonacciWitness2 = PlusSucc<PlusZero<N1>>
typealias Fibonacci2 = FibonacciStep<Fibonacci1, FibonacciWitness2>
typealias FibonacciWitness3 = PlusSucc<PlusSucc<PlusZero<N1>>>
typealias Fibonacci3 = FibonacciStep<Fibonacci2, FibonacciWitness3>
}
6. Golden ratio and Fibonacci
The golden ratio φ has CF [1; 1, 1, ...] — the Fibonacci recurrence. Convergents satisfy hn = F(n+2), kn = F(n+1).
@GoldenRatioProof(depth: 5)
enum GoldenRatioProof {}
assertEqual(GoldenRatioProof.Convergent0.P.self, N1.self) // h_0 = F(2) = 1
assertEqual(GoldenRatioProof.Convergent1.P.self, N2.self) // h_1 = F(3) = 2
assertEqual(GoldenRatioProof.Convergent3.P.self, N5.self) // h_3 = F(5) = 5
assertEqual(GoldenRatioProof.Convergent5.P.self, N13.self) // h_5 = F(7) = 13
Macro expansion (depth: 1)
@GoldenRatioProof(depth: 1)
enum GoldenRatioProof {}
enum GoldenRatioProof {
// Fibonacci chain
typealias Fibonacci1 = FibonacciStep<Fibonacci0, ...>
typealias Fibonacci2 = FibonacciStep<Fibonacci1, ...>
typealias Fibonacci3 = FibonacciStep<Fibonacci2, ...>
// CF convergents
typealias Convergent0 = GCFConvergent0<N1>
typealias Convergent1 = GCFConvergentStep<Convergent0, ...>
func goldenRatioCorrespondenceCheck() {
assertEqual(Convergent0.P.self, Fibonacci2.Current.self)
assertEqual(Convergent1.P.self, Fibonacci3.Current.self)
}
}
7. sqrt(2) CF and matrix construction
The CF for √2 is [1; 2, 2, ...]. The recurrence hn = 2hn-1 + hn-2 is equivalently left-multiplication by M = [[2,1],[1,0]]. The macro constructs both and proves they match.
@Sqrt2ConvergenceProof(depth: 3)
enum Sqrt2ConvergenceProof {}
assertEqual(Sqrt2ConvergenceProof.Convergent1.P.self, N3.self) // h_1 = 3
assertEqual(Sqrt2ConvergenceProof.Convergent2.P.self, N7.self) // h_2 = 7
assertEqual(Sqrt2ConvergenceProof.Convergent3.P.self, N17.self) // h_3 = 17
assertEqual(Sqrt2ConvergenceProof.MatrixPower3.A.self, N17.self) // matrix matches
8. Universal addition theorems
Sections 3–7 proved specific numerical facts: this convergent equals that value. Universal theorems prove statements for all natural numbers at once, via structural induction: a base case on Zero/PlusZero and an inductive step on AddOne/PlusSucc.
The generic functions below prove universality. When the compiler accepts useLeftZero(N0.self), useLeftZero(N5.self), and useLeftZero(N9.self), it's not just checking three cases — the generic constraint <N: AddLeftZero> means it accepts any natural number. If there were a natural number that didn't satisfy the theorem, the conditional conformance chain would break and the generic call would fail to compile.
Each theorem defines an associated type that transforms one proof into another, following the same type-alias-resolution mechanism as .Total (section 2).
.Shifted (Theorem 2) takes a proof that a + b = c and produces a proof that S(a) + b = S(c). The base case: PlusZero<N>.Shifted = PlusZero<AddOne<N>> — bump N to S(N). The inductive step: PlusSucc<P>.Shifted = PlusSucc<P.Shifted> — shift the inner proof, then re-wrap. So PlusSucc<PlusZero<N2>>.Shifted resolves to PlusSucc<PlusZero<N3>> — a proof that 3 + 1 = 4.
.Commuted (Theorem 3) takes a proof that a + b = c and produces a proof that b + a = c. The base case: PlusZero<N>.Commuted = N.ZeroPlusProof — flip N + 0 to 0 + N using Theorem 1. The inductive step: PlusSucc<P>.Commuted = P.Commuted.Shifted — commute the inner proof, then shift. Commutativity is built from the first two theorems.
Theorem 1: 0 + n = n
func useLeftZero<N: AddLeftZero>(_: N.Type) {}
useLeftZero(N0.self); useLeftZero(N5.self); useLeftZero(N9.self)
assertEqual(N5.ZeroPlusProof.Left.self, Zero.self)
assertEqual(N5.ZeroPlusProof.Total.self, N5.self)
Theorem 2: a + b = c ⇒ S(a) + b = S(c)
typealias TwoPlusTwo = PlusSucc<PlusSucc<PlusZero<N2>>>
assertEqual(TwoPlusTwo.Shifted.Left.self, N3.self)
assertEqual(TwoPlusTwo.Shifted.Total.self, N5.self)
Theorem 3: Commutativity
assertEqual(PlusZero<N7>.Commuted.Left.self, N0.self)
assertEqual(PlusZero<N7>.Commuted.Total.self, N7.self)
Protocol definitions (from AdditionTheorems.swift)
public protocol AddLeftZero: Natural {
associatedtype ZeroPlusProof: NaturalSum
}
extension Zero: AddLeftZero {
public typealias ZeroPlusProof = PlusZero<Zero>
}
extension AddOne: AddLeftZero where Predecessor: AddLeftZero {
public typealias ZeroPlusProof = PlusSucc<Predecessor.ZeroPlusProof>
}
public protocol AddCommutative: NaturalSum {
associatedtype Commuted: NaturalSum
}
extension PlusZero: AddCommutative where N: AddLeftZero {
public typealias Commuted = N.ZeroPlusProof
}
extension PlusSucc: AddCommutative
where Proof: AddCommutative, Proof.Commuted: SuccessorLeftAdd {
public typealias Commuted = Proof.Commuted.Shifted
}
9. Associativity of addition
(a + b) + c = a + (b + c) requires reasoning about two addition proofs simultaneously: one for a + b and another for the result plus c. But Swift's conditional conformance can only do induction on one type parameter at a time.
The seed technique solves this. ProofSeed<P> is a wrapper type that conforms to Natural while carrying a NaturalSum proof P inside. Its .AssociativeProof type alias is just P itself — the unwrapped proof. Each AddOne layer adds one PlusSucc: AddOne<X>.AssociativeProof = PlusSucc<X.AssociativeProof>. So AddOne<AddOne<ProofSeed<P>>>.AssociativeProof resolves to PlusSucc<PlusSucc<P>> — the original proof extended by two successor steps. The seed freezes the first parameter (the proof of a + b) so the second parameter (c) can vary freely.
This pattern recurs: ProductSeed (section 12) wraps a NaturalProduct proof for distributivity. Any time you need induction over two parameters, you can wrap one as a seed and induct over the other.
Wrap one proof as a seed, build AddOne layers for the second parameter.
// (3 + 2) + 4 = 3 + (2 + 4) = 9
typealias Associative3Plus2Plus4 = AddOne<AddOne<AddOne<AddOne<
ProofSeed<ThreePlusTwo>>>>>
assertEqual(Associative3Plus2Plus4.AssociativeProof.Left.self, N3.self)
assertEqual(Associative3Plus2Plus4.AssociativeProof.Right.self, N6.self)
assertEqual(Associative3Plus2Plus4.AssociativeProof.Total.self, N9.self)
typealias FivePlusFour = PlusSucc<PlusSucc<PlusSucc<PlusSucc<
PlusZero<N5>>>>>
assertEqual(Associative3Plus2Plus4.AssociativeProof.Total.self,
FivePlusFour.Total.self)
ProofSeed definition
public enum ProofSeed<P: NaturalSum>: Natural {
public typealias Successor = AddOne<Self>
public typealias Predecessor = SubOne<Zero>
}
public protocol AddAssociative: Natural {
associatedtype AssociativeProof: NaturalSum
}
extension ProofSeed: AddAssociative {
public typealias AssociativeProof = P
}
extension AddOne: AddAssociative where Predecessor: AddAssociative {
public typealias AssociativeProof = PlusSucc<Predecessor.AssociativeProof>
}
10. Universal multiplication theorems
An earlier encoding, TimesSucc, composed a product witness with a sum witness using where clauses. This works for individual proofs, but when used inside inductive protocols, each layer compounds the constraints the type checker must solve simultaneously. Swift's type checker uses a rewrite system to resolve associated types, and the exponentially growing constraint set overwhelms it — compilation fails. The flat encoding (TimesTick/TimesGroup) decomposes multiplication into where-clause-free primitives, sidestepping the problem entirely and enabling universal theorems.
To prove a * b, you build b groups of a ticks each. TimesTick adds 1 to Total (one step within a copy of the left operand). TimesGroup adds 1 to Right without changing Total (marking that one complete copy has been accumulated). After a ticks followed by a group boundary, one copy of a has been added; repeating b times gives a * b.
.Distributed transforms a proof of a * b = c into a proof of S(a) * b = c + b. Each conformance defines a Distributed type alias: TimesZero<N>.Distributed = TimesZero<AddOne<N>> (bump Left), TimesTick<P>.Distributed = TimesTick<P.Distributed> (pass through), TimesGroup<P>.Distributed = TimesGroup<TimesTick<P.Distributed>> (each group gains one extra tick for the new successor). So FlatProduct2Times3.Distributed resolves through the chain to a proof of 3 * 3 = 9.
Commutativity pairs two proofs of the same product. .ForwardProof directly constructs A * b with a hardcoded number of ticks per group (e.g., AddOne<Seed>.ForwardProof = TimesGroup<TimesTick<TimesTick<Seed.ForwardProof>>> for A = 2). .ReverseProof builds b * A by chaining .Distributed: AddOne<X>.ReverseProof = X.ReverseProof.Distributed. Each .Distributed step increments the left operand by one, building up from 0 * A = 0.
Flat encoding avoids where clauses that trigger rewrite system explosion.
// 2 * 3 = 6: three groups of two ticks
typealias FlatProduct2Times0 = TimesZero<N2>
typealias FlatProduct2Times1 = TimesGroup<TimesTick<TimesTick<FlatProduct2Times0>>>
typealias FlatProduct2Times2 = TimesGroup<TimesTick<TimesTick<FlatProduct2Times1>>>
typealias FlatProduct2Times3 = TimesGroup<TimesTick<TimesTick<FlatProduct2Times2>>>
assertEqual(FlatProduct2Times3.Total.self, N6.self)
// SuccessorLeftMultiplication: 2*3=6 => 3*3=9
assertEqual(FlatProduct2Times3.Distributed.Total.self, N9.self)
// Commutativity: 2*3 = 3*2 = 6
typealias MultiplicationCommutativity2Times3 =
AddOne<AddOne<AddOne<MultiplicationCommutativityOfTwoSeed>>>
assertEqual(MultiplicationCommutativity2Times3.ForwardProof.Total.self,
MultiplicationCommutativity2Times3.ReverseProof.Total.self)
@MultiplicationCommutativityProof(leftOperand: 4, depth: 5)
enum MultiplicationCommutativity4 {}
assertEqual(MultiplicationCommutativity4.Forward3.Total.self, N12.self)
Macro expansion (leftOperand: 2, depth: 2)
@MultiplicationCommutativityProof(
leftOperand: 2, depth: 2)
enum MultiplicationCommutativity2 {}
enum MultiplicationCommutativity2 {
// Forward: 2 * b (flat encoding)
typealias Forward0 = TimesZero<N2>
typealias Forward1 = TimesGroup<TimesTick<TimesTick<Forward0>>>
typealias Forward2 = TimesGroup<TimesTick<TimesTick<Forward1>>>
// Reverse: b * 2 (via SuccessorLeftMultiplication)
typealias Reverse0 = N2.ZeroTimesProof
typealias Reverse1 = Reverse0.Distributed
typealias Reverse2 = Reverse1.Distributed
}
Protocol definitions (from MultiplicationTheorems.swift)
public struct TimesTick<Proof: NaturalProduct>: NaturalProduct {
public typealias Left = Proof.Left
public typealias Right = Proof.Right
public typealias Total = AddOne<Proof.Total>
}
public struct TimesGroup<Proof: NaturalProduct>: NaturalProduct {
public typealias Left = Proof.Left
public typealias Right = AddOne<Proof.Right>
public typealias Total = Proof.Total
}
public protocol SuccessorLeftMultiplication: NaturalProduct {
associatedtype Distributed: NaturalProduct & SuccessorLeftMultiplication
}
11. Coinductive streams
The bounded proofs in sections 3–7 represent irrational numbers through finite convergent chains (macro-generated). Coinductive streams provide a complementary representation: the continued fraction coefficient sequence itself as a type. Where the convergent proofs compute specific rational approximations, the stream is the irrational number — an infinite object encoded in a finite type definition.
A CFStream has a Head (the current coefficient) and a Tail (the rest of the stream). For periodic CFs, self-referential types create productive fixed points — corecursion in the type system. Swift resolves the self-reference lazily, so .Tail.Tail...Head always terminates.
The stream coefficients match the values used by the macro-generated convergent proofs. For the golden ratio, PhiCF.Head = N1 matches the all-ones CF used in section 6. For √2, Sqrt2CF encodes [1; 2, 2, ...], the same CF computed by section 7. The streams encode the identity of the number; the convergent proofs compute its approximations.
PhiCF is self-referential. Sqrt2CF enters the Sqrt2Periodic loop.
assertEqual(PhiCF.Head.self, N1.self)
assertEqual(PhiCF.Tail.Tail.Head.self, N1.self)
assertStreamEqual(PhiCF.Tail.self, PhiCF.self)
assertEqual(Sqrt2CF.Head.self, N1.self)
assertEqual(Sqrt2CF.Tail.Head.self, N2.self)
assertStreamEqual(Sqrt2CF.Tail.self, Sqrt2Periodic.self)
// Universal unfold: PhiCF unfolds to itself at any depth
func usePhiUnfold<N: PhiUnfold>(_: N.Type) {}
usePhiUnfold(AddOne<AddOne<AddOne<PhiUnfoldSeed>>>.self)
assertStreamEqual(AddOne<AddOne<PhiUnfoldSeed>>.Unfolded.self,
PhiCF.self)
12. Distributivity
a * (b + c) = a*b + a*c bridges sum and product witnesses. The product proof for a * (b + c) is built as c groups of a ticks on top of ProductSeed<Q>, where Q witnesses a * b. Each conformance defines a DistributiveSum type alias — a sum witness that rides along inside the product proof:
ProductSeed<Q>.DistributiveSum = PlusZero<Q.Total>— the sum begins at a*b + 0 (no a*c contribution yet).TimesTick<P>.DistributiveSum = PlusSucc<P.DistributiveSum>— each tick adds 1 to the right side (the a*c portion grows by 1).TimesGroup<P>.DistributiveSum = P.DistributiveSum— group boundaries pass the sum through unchanged.
After c groups of a ticks, the right side of the sum has accumulated a*c, so DistributiveSum witnesses a*b + a*c = a*(b+c).
DistributiveSum (orange) tracked through the product proof (blue).
// 2 * (1 + 1) = 2*1 + 2*1 = 4
typealias Distributive2Times1Plus1 = TimesGroup<TimesTick<TimesTick<
ProductSeed<FlatProduct2Times1>>>>
assertEqual(Distributive2Times1Plus1.DistributiveSum.Left.self, N2.self) // a*b = 2
assertEqual(Distributive2Times1Plus1.DistributiveSum.Right.self, N2.self) // a*c = 2
assertEqual(Distributive2Times1Plus1.DistributiveSum.Total.self, N4.self) // 2+2 = 4
// 3 * (2 + 1) = 3*2 + 3*1 = 9
typealias FlatProduct3Times2 = TimesGroup<TimesTick<TimesTick<TimesTick<
TimesGroup<TimesTick<TimesTick<TimesTick<TimesZero<N3>>>>>>>>>
typealias Distributive3Times2Plus1 = TimesGroup<TimesTick<TimesTick<TimesTick<
ProductSeed<FlatProduct3Times2>>>>>
assertEqual(Distributive3Times2Plus1.DistributiveSum.Left.self, N6.self) // 3*2
assertEqual(Distributive3Times2Plus1.DistributiveSum.Right.self, N3.self) // 3*1
assertEqual(Distributive3Times2Plus1.DistributiveSum.Total.self, N9.self) // 6+3
ProductSeed and MultiplicationDistributive definitions
public enum ProductSeed<Q: NaturalProduct>: NaturalProduct {
public typealias Left = Q.Left
public typealias Right = Q.Right
public typealias Total = Q.Total
}
public protocol MultiplicationDistributive: NaturalProduct {
associatedtype DistributiveSum: NaturalSum
}
extension ProductSeed: MultiplicationDistributive {
public typealias DistributiveSum = PlusZero<Q.Total>
}
extension TimesTick: MultiplicationDistributive where Proof: MultiplicationDistributive {
public typealias DistributiveSum = PlusSucc<Proof.DistributiveSum>
}
extension TimesGroup: MultiplicationDistributive where Proof: MultiplicationDistributive {
public typealias DistributiveSum = Proof.DistributiveSum
}
13. Number-theoretic identities
Distributivity and SuccessorLeftMultiplication explain why identities hold, not just that they hold. Both sides of each identity decompose via a shared base product, and the tools from sections 10 and 12 provide the witnesses.
Difference of squares: n(n+2) + 1 = (n+1)²
Both sides share the base product n*(n+1). The right side is (n+1)² = n*(n+1) + (n+1) via SuccessorLeftMultiplication. The left side is n*(n+2) = n*((n+1)+1) = n*(n+1) + n via distributivity. The remainders differ by exactly 1.
FlatProduct1Times2 witnesses 1 * 2 = 2 (the shared base for n=1). Applying .Distributed gives 2 * 2 = 4. The left side DifferenceOfSquares1 witnesses 3 + 1 = 4 via PlusSucc<PlusZero<N3>>, and the type checker confirms both totals are 4.
// n=1: 1*3 + 1 = 4 = 2^2
typealias DifferenceOfSquares1 = PlusSucc<PlusZero<N3>>
assertEqual(DifferenceOfSquares1.Total.self,
FlatProduct1Times2.Distributed.Total.self)
Cassini identity: F(n-1)F(n+1) - F(n)² = (-1)n
The key step uses distributivity: F(n+1)*F(n-1) = (F(n)+F(n-1))*F(n-1) = F(n)*F(n-1) + F(n-1)². This relates the cross-product at step n to the square and cross-product at step n-1. At n=4: F(3)=2, F(4)=3, F(5)=5, so F(3)*F(5) = 2*5 = 10 and F(4)² + 1 = 9 + 1 = 10.
FlatProduct2Times5 witnesses 2 * 5 = 10 (built by extending FlatProduct2Times3 from section 10 with two more groups). CassiniIdentity4 witnesses 9 + 1 = 10 via PlusSucc<PlusZero<N9>>.
// n=4: F(3)*F(5) = F(4)^2 + 1 => 2*5 = 9+1 = 10
typealias CassiniIdentity4 = PlusSucc<PlusZero<N9>>
assertEqual(CassiniIdentity4.Total.self, FlatProduct2Times5.Total.self)
14. Convergent determinant identity
The convergent determinant hnkn-1 - hn-1kn = (-1)n+1 connects CF matrices to number theory. For the golden ratio, this reduces to Cassini. For √2, with convergents h0/k0 = 1/1, h1/k1 = 3/2, h2/k2 = 7/5:
The products are built by chaining .Distributed from OneTimesProof. Each .Distributed increments the left operand by one: N1.OneTimesProof witnesses 1 * 1 = 1, the first .Distributed gives 2 * 1 = 2, and the second gives 3 * 1 = 3. Similarly, N5.OneTimesProof.Distributed.Distributed builds 1 * 5 = 5 → 2 * 5 = 10 → 3 * 5 = 15.
// n=1: h_1*k_0 = h_0*k_1 + 1 => 3*1 = 1*2 + 1 => 3 = 3
// N1.OneTimesProof: 1*1=1, .Distributed: 2*1=2, .Distributed: 3*1=3
typealias Sqrt2Det_3x1 = N1.OneTimesProof.Distributed.Distributed
typealias Sqrt2DetWit1 = PlusSucc<PlusZero<N2>>
assertEqual(Sqrt2DetWit1.Total.self, Sqrt2Det_3x1.Total.self)
// n=2: h_1*k_2 = h_2*k_1 + 1 => 3*5 = 7*2 + 1 => 15 = 15
// N5.OneTimesProof: 1*5=5, .Distributed: 2*5=10, .Distributed: 3*5=15
typealias Sqrt2Det_3x5 = N5.OneTimesProof.Distributed.Distributed
typealias Sqrt2DetWit2 = PlusSucc<PlusZero<AddOne<N13>>>
assertEqual(Sqrt2DetWit2.Total.self, Sqrt2Det_3x5.Total.self)
15. Three pi correspondences
The codebase proves three representations of pi independently: Brouncker's CF (section 3), the Leibniz series (section 3), and the Wallis product (section 4). This section connects all three.
Brouncker–Leibniz (proved by the @PiConvergenceProof macro): at each depth k, the CF convergent numerator equals the Leibniz denominator and vice versa: CFk.P = LSk+1.Q. This was verified in section 3.
Wallis–Leibniz (proved here): each Wallis denominator is the product of two consecutive Leibniz denominators: WQ[k] = LQ[k+1] * LQ[k]. Leibniz denominators accumulate one odd factor per step (1, 3, 15, 105, ...), while Wallis denominators accumulate paired odd factors — the product telescopes.
Brouncker–Wallis (derived): combining the two, WQ[k] = CFk.P * CFk-1.P. Each Wallis denominator is the product of two consecutive CF numerators. This closes the triangle.
// k=1: WQ[1] = 3*1 = 3
typealias WallisLeibniz3Times1 = N3.TimesOneProof
assertEqual(WallisLeibniz3Times1.Total.self,
WallisProductProof.Wallis1.Q.self)
// k=2: WQ[2] = 15*3 = 45
typealias WallisLeibniz1Times3 = N3.OneTimesProof
typealias WallisLeibniz15Times3 = WallisLeibniz1Times3
.Distributed.Distributed.Distributed.Distributed
.Distributed.Distributed.Distributed.Distributed
.Distributed.Distributed.Distributed.Distributed
.Distributed.Distributed
assertEqual(WallisLeibniz15Times3.Total.self,
WallisProductProof.Wallis2.Q.self) // 45
Three-way connection
Three representations of pi, connected by type-level proofs.
// Brouncker-Leibniz: Convergent_k.P = LeibnizSum_{k+1}.Q
assertEqual(PiConvergenceProof.Convergent1.P.self,
PiConvergenceProof.LeibnizSum2.Q.self) // 3 = 3
assertEqual(PiConvergenceProof.Convergent0.P.self,
PiConvergenceProof.LeibnizSum1.Q.self) // 1 = 1
assertEqual(PiConvergenceProof.Convergent2.P.self,
PiConvergenceProof.LeibnizSum3.Q.self) // 15 = 15
// Brouncker-Wallis (derived): WQ[k] = CF_k.P * CF_{k-1}.P
// At k=1: WQ[1] = Convergent1.P * Convergent0.P = 3 * 1 = 3
assertEqual(WallisLeibniz3Times1.Left.self,
PiConvergenceProof.Convergent1.P.self)
assertEqual(WallisLeibniz3Times1.Right.self,
PiConvergenceProof.Convergent0.P.self)
16. Unified determinant identity
Section 13 proved the Cassini identity for the golden ratio CF by hand. Section 14 proved the convergent determinant for √2 by hand. Both follow the same pattern: at each step n, hnkn-1 - hn-1kn = (-1)n+1. They were always the same theorem — applied to different coefficients.
The @CFDeterminantProof macro makes the unification explicit. Given any continued fraction's coefficients, it computes convergents internally, generates shared SuccessorLeftMultiplication chains grouped by right operand, and emits determinant sum witnesses at each step. The same macro, applied to [1, 1, 1, 1, 1] or [1, 2, 2, 2], recovers both identities.
This section ties together nearly every technique from the tutorial:
- Witness chains (section 2): the determinant sums are
PlusSucc/PlusZerochains, exactly the addition witnesses introduced at the start. - Macros as proof search (section 3): the macro computes convergents and products at compile time, then emits witness types for the type checker to verify — the same search/verify split that powers the pi and Fibonacci proofs.
- Universal multiplication theorems (section 10): the products hn * kn-1 and hn-1 * kn are built from
OneTimesProofand chained.Distributed— the same universal theorems that eliminated per-factor product chains in the golden ratio and √2 macros. - Distributivity and difference of squares (sections 12–13): the Cassini identity decomposes F(n+1) * F(n-1) via distributivity, and the factor correspondence uses the difference-of-squares identity — both are structural decompositions that the determinant identity generalizes.
- Cross-verification: the
assertEqualcalls below compare the macro's convergents againstGoldenRatioProof(section 6) andSqrt2ConvergenceProof(section 7). Two independently generated proofs — one from the constant-specific macro, one from the unified macro — must produce identical types.
Where earlier sections proved individual identities, this section shows them as instances of one parametric structure. The coefficients change; the proof shape doesn't.
// Golden ratio: [1; 1, 1, 1, 1] -- Cassini's identity
@CFDeterminantProof(coefficients: [1, 1, 1, 1, 1])
enum GoldenRatioDeterminant {}
// Cross-check: macro convergents match the golden ratio proof's
assertEqual(GoldenRatioDeterminant.HTimesPrevK3.Left.self,
GoldenRatioProof.Convergent3.P.self) // h_3 = 5
assertEqual(GoldenRatioDeterminant.PrevHTimesK3.Left.self,
GoldenRatioProof.Convergent2.P.self) // h_2 = 3
// sqrt(2): [1; 2, 2, 2] -- same identity, different coefficients
@CFDeterminantProof(coefficients: [1, 2, 2, 2])
enum Sqrt2Determinant2 {}
// Cross-check: convergents match the sqrt(2) convergence proof's
assertEqual(Sqrt2Determinant2.HTimesPrevK2.Left.self,
Sqrt2ConvergenceProof.Convergent2.P.self) // h_2 = 7
assertEqual(Sqrt2Determinant2.PrevHTimesK2.Left.self,
Sqrt2ConvergenceProof.Convergent1.P.self) // h_1 = 3
Macro expansion (coefficients: [1, 1])
@CFDeterminantProof(coefficients: [1, 1])
enum PhiDet {}
enum PhiDet {
// Chains grouped by right operand (here Right=1)
typealias Chain1x1 = AddOne<Zero>.OneTimesProof // 1*1
typealias Chain1x2 = Chain1x1.Distributed // 2*1
// Step 0 (even): h_{-1}*k_0=1*1, h_0*k_{-1}=1*0
typealias HTimesPrevK0 = TimesZero<AddOne<Zero>> // 1*0 = 0
typealias PrevHTimesK0 = Chain1x1 // 1*1 = 1
typealias Determinant0 = PlusSucc<PlusZero<Zero>> // 0+1 = 1
// Step 1 (odd): h_1*k_0=2*1, h_0*k_1=1*1
typealias HTimesPrevK1 = Chain1x2 // 2*1 = 2
typealias PrevHTimesK1 = Chain1x1 // 1*1 = 1
typealias Determinant1 = PlusSucc<PlusZero<N1>> // 1+1 = 2
func determinantCheck() {
assertEqual(Determinant0.Total.self, PrevHTimesK0.Total.self)
assertEqual(Determinant1.Total.self, HTimesPrevK1.Total.self)
}
}
Epilogue
Everything you've read above is verified by the Swift compiler. The source file compiles and exits cleanly — every assertEqual call unifies its type arguments, and every witness type satisfies its protocol constraints. There is no runtime computation. The proof is the compilation itself.
The compiler verified 100+ mathematical facts: natural number arithmetic, continued fractions, the Leibniz series, the Wallis product, the golden ratio / Fibonacci correspondence, the √2 CF / matrix construction, four universal addition theorems (left zero identity, successor-left shift, commutativity, associativity), six universal multiplication theorems (left zero annihilation, successor-left multiplication, per-A commutativity, multiplicative identities, distributivity), number-theoretic identities (difference of squares, Cassini, convergent determinant, and a unified CF determinant identity macro covering both constants), three pi correspondences (Brouncker–Leibniz, Wallis–Leibniz, Brouncker–Wallis), and coinductive streams for irrational numbers.
To verify it yourself: swift build && swift run AbuseOfNotationClient.