Chapters

Proving Equalities

Use equality propositions, reflexivity, and contradiction.

14.1 a = b is evidence that values are equal

ch14-refl.taut
import std::Base

theorem two_plus_two(): (2: Nat) + 2 = 4 {
  Refl
}

// Reflexivity suffices because both sides compute to four.
✓ Check passed
✓ theorem two_plus_two : 2 + 2 = 4

a = b states that two terms of type Nat are equal. Refl provides reflexivity: a term equals itself. Because 2 + 2 and 4 reduce to the same form by computation, the kernel accepts this proof.

Do not confuse == with a = b. The former is an equality comparison used at runtime; the latter is a proposition in the proof layer.

An incorrect proposition is rejected.

ch14-wrong.taut
import std::Base

theorem wrong(): (2: Nat) + 2 = 5 {
  Refl
}

// This deliberately asks reflexivity to prove a false equality.
✗ Error
✗ wrong

error 4:3: type mismatch: expected `2 + 2 = 5`, found `2 + 2 = 2 + 2`
  differing part: expected `5`, found `4`

The actual type error returned by the current CLI appears below the code.

The diagnostic says that Refl proves (4: Nat) = 4, not 2 + 2 = 5. Reflexivity proves only that the results of computation coincide.

14.2 Chain equalities

ToolMeaning
ReflProve a = a
Eq::sym(e)Obtain b = a from a = b
Eq::trans(e1, e2)Obtain a = c from a = b and b = c
Eq::congr(f, e)Obtain f(a) = f(b) from a = b
Eq::subst(...)Substitute equal terms

The proposition to be proved determines the type and terms of Refl. Write simply Refl in a proof.

ch14-tools.taut
import std::Base

theorem sym_ex(a: Nat, b: Nat, e: a = b): b = a {
  Eq::sym(e)
}

theorem trans_ex(
  a: Nat,
  b: Nat,
  c: Nat,
  e1: a = b,
  e2: b = c
): a = c {
  Eq::trans(e1, e2)
}

theorem congr_ex(a: Nat, b: Nat, e: a = b): Succ(a) = Succ(b) {
  Eq::congr(Succ, e)
}

// Use symmetry, transitivity, and congruence explicitly.
✓ Check passed
✓ theorem sym_ex : ∀ a: Nat. ∀ b: Nat. (a = b) -> b = a
✓ theorem trans_ex : ∀ a: Nat. ∀ b: Nat. ∀ c: Nat. (a = b) -> (b = c) -> a = c
✓ theorem congr_ex : ∀ a: Nat. ∀ b: Nat. (a = b) -> Succ(a) = Succ(b)

14.3 Assume an impossible equality

ch14-absurd.taut
import std::Base

theorem two_plus_two_ne_five(h: (2: Nat) + 2 = 5): False {
  match h: False {
  }
}

// An impossible equality has no constructor to match.
✓ Check passed
✓ theorem two_plus_two_ne_five : ¬(2 + 2 = 5)

This theorem states that evidence for 2 + 2 = 5 would lead to a contradiction. Since 2 + 2 reduces to 4, (4: Nat) = 5 cannot match any form of Refl. A match with no arms expresses that there are no possible constructors.

14.4 What adding an axiom means

Declaring an axiom adopts an unproved assumption. That is not inherently illegitimate. For example, you can add the law of excluded middle as an axiom:

ch14-axiom.taut
import std::Base

axiom em(P: Prop): P ∨ ¬P

theorem not_not(P: Prop, n: ¬¬P): P {
  Or::elim(em(P), p => p, np => False::elim(n(np)))
}

// Record the dependency on excluded middle rather than hiding the axiom.
✓ Check passed
✓ axiom em : ∀ P: Prop. Or(P, ¬P)
✓ theorem not_not : ∀ P: Prop. (¬(¬P)) -> P
ok      em (axioms: em)
ok      not_not (axioms: em)

The output records that not_not depends on the axiom em. A theorem using axioms is a result conditional on those axioms. Distinguish being proved from holding without axioms.

Exercise 14.1: Complete theorem reflexive(x: Nat): x = x.