Induction for proofs

an illustration in 5 languages

2025 James B. Wilson

Dedekind-Peano Induction

\(P(0)\)

\(P(k)\to P(++k)\)

\(n:\mathbb{N}\)

\(P(n)\)

"Base Case"

"Induction Hypothesis"

"Number to use"

"Consequence"

1 is odd.

If k is odd then k+2 is odd.

4=2+2

Nothing to say, 4=2+2 doesn't match the induction hypothesis nor the base case.

1 is odd.

If k is odd then k+2 is odd.

5=(1+2)+2

If 1+2 is odd then (1+2)+2 is odd.

    If 1 is odd then 1+2 is odd.

        1 is odd.

5 is odd.

// Form the type
sealed trait List[A] 
// Introduce base case
case []  extends List[A]
// Introduct Induction Hypothesis
case (head : A) :: (tail : List[A]) extends List[A]

def length( as : List[A]) : Int =
	// Eliminate inductive type
    as match {
    	// Compute base case, 0 lenght
        case [] -> 0         
        // Compute Resursion via "Inductive Hypothesis":
        // if length(tail)=k then length(head::tail) = ++k
        case head :: tail -> ++length(tail) 
    }

Strong Induction

\(P(0)\)

\(P(0),....,P(k)\to P(++k)\)

\(n:\mathbb{N}\)

\(P(n)\)

"Base Case"

"Induction Hypothesis"

"Number to use"

"Consequence"

A triangle has 180 degrees.

If k-gon has \(180(k-2)\) degree then a (k+1)-gon has \(180((k+1)-2)\) degrees.

n=5=++(++3)=(3+1)+1
 

// Form the type
sealed trait List[A] 
// Introduce base case
case []  extends List[A]
// Introduct Induction Hypothesis
case (head : A) :: (tail : List[A]) extends List[A]

def length( as : List[A]) : Int =
	// Eliminate inductive type
    as match {
    	// Compute base case, 0 lenght
        case [] -> 0         
        // Compute Resursion via "Inductive Hypothesis":
        // if length(tail)=k then length(head::tail) = ++k
        case head :: tail -> ++length(tail) 
    }

Noetherian Induction

Every subset \(F\) of events \(E\) has minimal ones \(\partial E\)

If \(P(\partial F)\) then \(P(F)\)

\(P(\partial E)\)

\(e\in E\)

\(P(e)\)

"Boundary Case"

"Induction Hypothesis"

"Event to use"

"Consequence"

"Well-founded"

E=Computer network.

If you can hack one user then you can hack his inbox.

Someone hacked you.

Your entire network is hacked; and you have just been fired.

def guess(n) =
  n match {
	case Even -> return "You loose"
	case Odd -> guess(n div 3)
	case Zero -> return "You win!"
  }

Guess.

> guess(6)
You loose
> guess(5)
   guess(1)
    guess(0)
You loose
> guess(0)
You loose
def guess(n) =
  n match {
	case Zero -> return "You win!"
    case Even -> return "You loose"
	case Odd -> guess(n div 3)
  }

Guess.

> guess(6)
You loose
> guess(5)
   guess(1)
    guess(0)
You win
> guess(0)
You win

\(\vdash \mathbb{B}:\text{Set}\)            (Form\(_{\mathbb{B}}\))

\(\vdash 0:\mathbb{B}\)            (Intro\(_{0-\mathbb{B}}\))

\(\vdash 1:\mathbb{B}\)            (Intro\(_{1-\mathbb{B}}\))

\(\begin{array}{rl} \Gamma & \vdash b:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} : P(b)\end{array}\)  (Elim\(_{\mathbb{B}}\))

\(\begin{array}{rl} \Gamma & \vdash 0:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} := x\end{array}\)  (0-Comp\(_{\mathbb{B}}\))

open import Data.Nat using (ℕ)
open import Data.String using (String)
open import Data.Sum using (_⊎_; inj₁; inj₂)

module Bits where 
   
-- Binary digits up ↑ and down ↓
data 𝔹 : Set where
  ↑ : 𝔹
  ↓ : 𝔹

-- Use of a bit
asFive : 𝔹 → ℕ ⊎ String
asFive ↑ = inj₁ 5
asFive ↓ = inj₂ "Five"

\(\begin{array}{rl} \Gamma & \vdash 1:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} := y\end{array}\)  (1-Comp\(_{\mathbb{B}}\))

Agda (1999)  (a Haskell family 1990)

open import Data.Nat using (ℕ)
open import Data.String using (String)
open import Data.Sum using (_⊎_; inj₁; inj₂)

module Bits where 
   
-- Binary digits up ↑ and down ↓
data Trit : Set where
  a : Trit
  b : Trit
  c : Trit

-- Do something
thing : Trit → ℕ ⊎ String
thing a = inj₁ 5
thing c = inj₂ "Five"
thing _ = inj₁ 42

Agda (1999)  (a Haskell family 1990)

\(\vdash \mathbb{B}:\text{Set}\)            (Form\(_{\mathbb{B}}\))

\(\vdash 0:\mathbb{B}\)            (Intro\(_{0-\mathbb{B}}\))

\(\vdash 1:\mathbb{B}\)            (Intro\(_{1-\mathbb{B}}\))

\(\begin{array}{rl} \Gamma & \vdash b:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} : P(b)\end{array}\)  (Elim\(_{\mathbb{B}}\))

\(\begin{array}{rl} \Gamma & \vdash 0:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} := x\end{array}\)  (0-Comp\(_{\mathbb{B}}\))

(* Binary digits up and down *)
type bit = 
  | Up
  | Down

(* Sum type for either natural number or string *)
type nat_or_string = 
  | Left of int
  | Right of string

(* Use of a bit. *)
let asFive = function
  | Up -> Left 5
  | Down -> Right "Five"

\(\begin{array}{rl} \Gamma & \vdash 1:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} := y\end{array}\)  (1-Comp\(_{\mathbb{B}}\))

OCaml (1996) (an ML family 1973)

\(\vdash \mathbb{B}:\text{Set}\)            (Form\(_{\mathbb{B}}\))

\(\vdash 0:\mathbb{B}\)            (Intro\(_{0-\mathbb{B}}\))

\(\vdash 1:\mathbb{B}\)            (Intro\(_{1-\mathbb{B}}\))

\(\begin{array}{rl} \Gamma & \vdash b:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} : P(b)\end{array}\)  (Elim\(_{\mathbb{B}}\))

\(\begin{array}{rl} \Gamma & \vdash 0:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} := x\end{array}\)  (0-Comp\(_{\mathbb{B}}\))

// Binary digits up and down
sealed trait 𝔹
case object ↑ extends 𝔹
case object ↓ extends 𝔹

// Use of a bit
def asFive(bit: 𝔹): Either[Int, String] = bit match {
  case ↑ => Left(5)
  case ↓ => Right("Five")
}

\(\begin{array}{rl} \Gamma & \vdash 1:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} := y\end{array}\)  (1-Comp\(_{\mathbb{B}}\))

Scala (2004) (an Java family 1995)

\(\vdash \mathbb{B}:\text{Set}\)            (Form\(_{\mathbb{B}}\))

\(\vdash 0:\mathbb{B}\)            (Intro\(_{0-\mathbb{B}}\))

\(\vdash 1:\mathbb{B}\)            (Intro\(_{1-\mathbb{B}}\))

\(\begin{array}{rl} \Gamma & \vdash b:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} : P(b)\end{array}\)  (Elim\(_{\mathbb{B}}\))

\(\begin{array}{rl} \Gamma & \vdash 0:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} := x\end{array}\)  (0-Comp\(_{\mathbb{B}}\))

import java.util.Objects;

class Bits {
    // Binary digits up and down using sealed interface
    sealed interface 𝔹 permits Up, Down {}
    final class Up() implements 𝔹 {}    
    final class Down implements 𝔹 {}
    
    // Sum type equivalent using sealed interface
    sealed interface Either<L, R> permits Left, Right {}
    record Left<L, R>(L value) implements Either<L, R> {}
    record Right<L, R>(R value) implements Either<L, R> {}
    
    // Use of a bit.
    Either<Integer, String> asFive(𝔹 bit) {
        return switch (bit) {
            case Up up -> new Left<>(5);
            case Down down -> new Right<>("Five");
        };
    }
}

\(\begin{array}{rl} \Gamma & \vdash 1:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} := y\end{array}\)  (1-Comp\(_{\mathbb{B}}\))

Java (since 1995,

but feature here added in 2021 (JDK 16))

\(\vdash \mathbb{B}:\text{Set}\)            (Form\(_{\mathbb{B}}\))

\(\vdash 0:\mathbb{B}\)            (Intro\(_{0-\mathbb{B}}\))

\(\vdash 1:\mathbb{B}\)            (Intro\(_{1-\mathbb{B}}\))

\(\begin{array}{rl} \Gamma & \vdash b:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} : P(b)\end{array}\)  (Elim\(_{\mathbb{B}}\))

\(\begin{array}{rl} \Gamma & \vdash 0:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} := x\end{array}\)  (0-Comp\(_{\mathbb{B}}\))

#include <variant>
#include <string>
#include <iostream>

// Binary digits up and down
enum class 𝔹 {
    ↑,  // up
    ↓   // down
};

// Sum type equivalent using std::variant
using EitherIntString = std::variant<int, std::string>;

// Use of a bit
EitherIntString asFive(𝔹 bit) {
    switch (bit) {
        case 𝔹::↑:
            return 5;
        case 𝔹::↓:
            return std::string("Five");
    }
    // Should never reach here with complete switch
    return 0;
}

\(\begin{array}{rl} \Gamma & \vdash 1:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} := y\end{array}\)  (1-Comp\(_{\mathbb{B}}\))

C++ (1983, but pattern matching still not standard, alternatives shown!)

\(\vdash \mathbb{B}:\text{Set}\)            (Form\(_{\mathbb{B}}\))

\(\vdash 0:\mathbb{B}\)            (Intro\(_{0-\mathbb{B}}\))

\(\vdash 1:\mathbb{B}\)            (Intro\(_{1-\mathbb{B}}\))

Logic: two independent possibilities.

data 𝔹 : Set where
  ↑ : 𝔹   -- Voltage up
  ↓ : 𝔹   -- Voltage down

Haskell Family (Agda, Idris)

type bit =
  | zero 
  | one

ML Family (OCaml)

sealed trait Bit
case Zero extends Bit
case  One extends Bit

Scala

enum Bit {
	ZERO, ONE;
}

Java (similar for C++)

abstract sealed class Bit {}
final class Zero() extends Bit {}
final class One() extends Bit {}

Java altneratives

public sealed class Bit 
    permits Zero, One {
    ...
}

\(\vdash \mathbb{B}:\text{Set}\)            (Form\(_{\mathbb{B}}\))

\(\begin{array}{rl} \Gamma & \vdash b:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} : P(b)\end{array}\)  (Elim\(_{\mathbb{B}}\))

{

}

\(\begin{array}{rl} \Gamma & \vdash 0:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} := x\end{array}\)  (0-Comp\(_{\mathbb{B}}\))

\(\begin{array}{rl} \Gamma & \vdash 1:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} := y\end{array}\)  (1-Comp\(_{\mathbb{B}}\))

\(\vdash 0:\mathbb{B}\)            (Intro\(_{0-\mathbb{B}}\))

\(\vdash 1:\mathbb{B}\)            (Intro\(_{1-\mathbb{B}}\))

not : Bit -> Bit
not zero = one
not  one = zero

Haskell / Idris / Agda

not b = match b with 
   | zero -> one
   |  one -> zero

OCaml

def not( b : Bit ) : Bit = 
   b match {
     case Zero() => One()
     case  One() => Zero()
   } 

Scala

select(b) {
  case Bit.ZERO -> Bit.ONE 
  case Bit.ONE -> Bit.ZERO
}

Java (similar for C++)

if ( b instanceof Zero )
   return new One();
if ( b instanceof One )
   return new Zero();

Java altneratives

Bit not( Bit b ) {
	return select( b ) {
        case Zero() -> new One();
        case  One() -> new Zero();
    }
}

\(\begin{array}{rl} \Gamma & \vdash b:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} : P(b)\end{array}\)  (Elim\(_{\mathbb{B}}\))

\(\begin{array}{rl} \Gamma & \vdash 0:\mathbb{B}\\ \Gamma & \vdash x:P(0)\\ \Gamma & \vdash y:P(1)\\ \hline \Gamma & \vdash \text{if }b \text{ then} = x\end{array}\)  (0-Comp\(_{\mathbb{B}}\))

\(A:Set\vdash \text{List}[A]:\text{Set}\)       (Form\(_{List}\))

\(\vdash []:List[A]\)            (Intro\(_{List}\))

\(a:A, tail:List[A]\vdash a::tail:List[A]\)            (Intro\(_{List}\))

Logic: Lists.

data List A where
  nil : List A
  cons : (a : A) -> (tail : List A) -> List A

Haskell / Idris / Agda

type 'a list =
  | Nil 
  | Cons of 'a * 'a list

OCaml

sealed trait List[A+]
case Nil extends List[Any]
case Cons( a : A, tail : List[A]) extends List[A]

Scala

Java (similar for C++)

public sealed class List<A> {}
class final Nil() extends List<Class> {}
class final Cons(<A> a, 
	List<A> tail) extends List<A> {}