Proof techniques crash course¶
Every proof you’ll ever write is some mix of a small number of standard moves. This chapter is a fast reference: each section names a classic technique, says how it maps onto an Algae rule, and then hands you three exercises to drill it.
Each exercise is a live editor seeded with a hole. Press Check ▶ to see the
open goal, click the hole (or press Ctrl-Space) for suggestions, and read
the hint if you’re stuck. Everything here uses only core — the rules you met
in Inference rules, Reasoning backward, and the tour
(The logical toolkit, Quantifiers and ⇔).
Here’s one worked all the way through, so you know what a finished answer looks like — the and-introduction from Reasoning backward:
rule and_intro(P Q : Prop)
⊢ P;
⊢ Q
────────────────────────
⊢ P ∧ Q
end;
lemma both(A B : Prop, x := A, y := B)
⊢ A ∧ B;
proof
by and_intro(A, B) cases
case ⊢ A; by x; qed;
case ⊢ B; by y; qed;
qed;
qed;
Now the techniques.
1. Direct proof¶
Goal: prove P. Method: apply a rule whose conclusion matches P,
then discharge whatever it leaves from your assumptions. A premise-free step (an
axiom or an assumption by h) closes a goal outright.
Exercises
1a. From a proof of A, build A ∨ B (the left injection).
rule or_intro_left(P Q : Prop)
⊢ P
────────────────────────
⊢ P ∨ Q
end;
lemma inject_left(A B : Prop, x := A)
⊢ A ∨ B;
proof
by wip(?goal);
wip;
Hint
or_intro_left(A, B) proves A ∨ B from ⊢ A — one premise,
so then ⊢ A;, closed by x.
1b. Same, but the right injection: from a proof of B, build A ∨ B.
rule or_intro_right(P Q : Prop)
⊢ Q
────────────────────────
⊢ P ∨ Q
end;
lemma inject_right(A B : Prop, y := B)
⊢ A ∨ B;
proof
by wip(?goal);
wip;
Hint
or_intro_right(A, B) proves A ∨ B from ⊢ B. then ⊢ B;
then by y.
1c. Take a conjunction apart: from a proof of A ∧ B, get A.
rule and_left(P Q : Prop)
⊢ P ∧ Q
────────────────────────
⊢ P
end;
lemma take_left(A B : Prop, h := A ∧ B)
⊢ A;
proof
by wip(?goal);
wip;
Hint
and_left(A, B) concludes ⊢ A from its one premise ⊢ A ∧ B.
then ⊢ A ∧ B; and close by h.
2. Conditional proof (implication)¶
Goal: prove P ⇒ Q. Method: assume P, then prove Q.
implication_intro does exactly this — it introduces the antecedent as a
named hypothesis after its premise P := P ⊢ Q.
Exercises
2a. Prove the identity implication A ⇒ A.
rule implication_intro(P Q : Prop)
P := P ⊢ Q
────────────────────────
⊢ P ⇒ Q
end;
lemma id(A : Prop)
⊢ A ⇒ A;
proof
by wip(?goal);
wip;
Hint
by implication_intro(A, A) then P := A ⊢ A; leaves you assuming
P : A and proving A — close it by P.
2b. If you already hold a proof of B, then A ⇒ B for any A.
rule implication_intro(P Q : Prop)
P := P ⊢ Q
────────────────────────
⊢ P ⇒ Q
end;
lemma const_imp(A B : Prop, q := B)
⊢ A ⇒ B;
proof
by wip(?goal);
wip;
Hint
implication_intro(A, B) then P := A ⊢ B; — ignore P and close
by q.
2c. Prove (A ∧ B) ⇒ A — assume the conjunction, then project.
rule implication_intro(P Q : Prop)
P := P ⊢ Q
────────────────────────
⊢ P ⇒ Q
end;
rule and_left(P Q : Prop)
⊢ P ∧ Q
────────────────────────
⊢ P
end;
lemma proj(A B : Prop)
⊢ (A ∧ B) ⇒ A;
proof
by wip(?goal);
wip;
Hint
implication_intro(A ∧ B, A) then P := A ∧ B ⊢ A; then
by and_left(A, B) then ⊢ A ∧ B; by P;.
3. Proof by cases¶
Goal: prove P. Method: split into all possible cases and prove P
in each. When the “all possible cases” is a disjunction A ∨ B you have,
or_elim is the tool: it gives you two branches, one assuming A and one
assuming B, both aiming at the same goal.
Exercises
3a. Disjunction commutes: from A ∨ B, prove B ∨ A.
rule or_elim(P Q R : Prop)
⊢ P ∨ Q;
P := P ⊢ R;
Q := Q ⊢ R
────────────────────────
⊢ R
end;
rule or_intro_left(P Q : Prop)
⊢ P
────────────────────────
⊢ P ∨ Q
end;
rule or_intro_right(P Q : Prop)
⊢ Q
────────────────────────
⊢ P ∨ Q
end;
lemma or_comm(A B : Prop, d := A ∨ B)
⊢ B ∨ A;
proof
by wip(?goal);
wip;
Hint
by or_elim(A, B, B ∨ A) cases gives three goals: re-prove
A ∨ B (by d), then a branch P := A (build B ∨ A with
or_intro_right) and a branch Q := B (with or_intro_left).
3b. A trivial-looking but instructive one: from A ∨ A, prove A.
rule or_elim(P Q R : Prop)
⊢ P ∨ Q;
P := P ⊢ R;
Q := Q ⊢ R
────────────────────────
⊢ R
end;
lemma idem(A : Prop, d := A ∨ A)
⊢ A;
proof
by wip(?goal);
wip;
Hint
or_elim(A, A, A) cases — feed d for the disjunction, and each
branch hands you an assumption of A to close with.
3c. The constructive dilemma: from A ∨ B, A ⇒ C and B ⇒ C,
conclude C.
rule or_elim(P Q R : Prop)
⊢ P ∨ Q;
P := P ⊢ R;
Q := Q ⊢ R
────────────────────────
⊢ R
end;
rule implication_elim(P Q : Prop)
⊢ P ⇒ Q;
⊢ P
────────────────────────
⊢ Q
end;
lemma dilemma(A B C : Prop, d := A ∨ B, f := A ⇒ C, g := B ⇒ C)
⊢ C;
proof
by wip(?goal);
wip;
Hint
or_elim(A, B, C) cases. In the P := A branch run
implication_elim(A, C) against f and P; symmetrically use g
and Q in the Q := B branch.
4. Proof by contradiction¶
Goal: prove ¬P. Method: assume P, and derive absurdity
(False). That’s negation_intro — it assumes P and asks you to reach
False. Two more tools travel with it: negation_elim turns a proof of P
and ¬P into False, and false_elim turns False into anything at
all (the principle of explosion).
Algae’s logic is intuitionistic
Classic “proof by contradiction” sometimes means proving a positive P by
assuming ¬P and deriving False. That step needs the law of excluded
middle, which core deliberately does not ship — so here contradiction
proves negations (¬P), and explosion carries a contradiction to any
goal. (This is also why the next section has no exercises.)
Exercises
5a. From A ⇒ False, prove ¬A.
rule negation_intro(P : Prop)
P := P ⊢ False
────────────────────────
⊢ ¬P
end;
rule implication_elim(P Q : Prop)
⊢ P ⇒ Q;
⊢ P
────────────────────────
⊢ Q
end;
lemma neg_from_imp(A : Prop, f := A ⇒ False)
⊢ ¬A;
proof
by wip(?goal);
wip;
Hint
negation_intro(A) then P := A ⊢ False; then run
implication_elim(A, False) against f and P.
5b. Explosion: from a proof of A and a proof of ¬A, prove any C.
rule false_elim(P : Prop)
⊢ False
────────────────────────
⊢ P
end;
rule negation_elim(P : Prop)
⊢ P;
⊢ ¬P
────────────────────────
⊢ False
end;
lemma explode(A C : Prop, x := A, nx := ¬A)
⊢ C;
proof
by wip(?goal);
wip;
Hint
false_elim(C) then ⊢ False; reduces the goal to False, which
negation_elim(A) produces from x and nx (two cases).
5c. Prove ¬¬A from A.
rule negation_intro(P : Prop)
P := P ⊢ False
────────────────────────
⊢ ¬P
end;
rule negation_elim(P : Prop)
⊢ P;
⊢ ¬P
────────────────────────
⊢ False
end;
lemma dni(A : Prop, x := A)
⊢ ¬(¬A);
proof
by wip(?goal);
wip;
Hint
negation_intro(¬A) then P := ¬A ⊢ False; — now you hold P : ¬A
and x : A, a contradiction, so negation_elim(A) gives False.
5. Proof by contrapositive¶
Goal: prove P ⇒ Q. Method: prove ¬Q ⇒ ¬P instead. core
provides this as the contrapositive rule: it takes your goal P ⇒ Q down to
the single premise ¬Q ⇒ ¬P.
This one is classical
Unlike everything else in this chapter, contrapositive is a classical
principle — turning ¬Q ⇒ ¬P back into P ⇒ Q rests on the law of
excluded middle. core ships it as a primitive rule so the technique is
available; the rest of the logic stays intuitionistic.
Exercises
6a. From ¬B ⇒ ¬A, prove A ⇒ B.
rule contrapositive(P Q : Prop)
⊢ ¬Q ⇒ ¬P
────────────────────────
⊢ P ⇒ Q
end;
lemma contra_direct(A B : Prop, nn := ¬B ⇒ ¬A)
⊢ A ⇒ B;
proof
by wip(?goal);
wip;
Hint
by contrapositive(A, B) then ⊢ ¬B ⇒ ¬A; by nn; — the rule turns
the goal straight into the hypothesis you hold.
6b. The mirror: from ¬A ⇒ ¬B, prove B ⇒ A.
rule contrapositive(P Q : Prop)
⊢ ¬Q ⇒ ¬P
────────────────────────
⊢ P ⇒ Q
end;
lemma contra_swap(A B : Prop, nn := ¬A ⇒ ¬B)
⊢ B ⇒ A;
proof
by wip(?goal);
wip;
Hint
by contrapositive(B, A) then ⊢ ¬A ⇒ ¬B; by nn; — mind the order:
the goal is B ⇒ A, so P = B and Q = A.
6c. Prove A ⇒ A the long way round — via its contrapositive.
rule contrapositive(P Q : Prop)
⊢ ¬Q ⇒ ¬P
────────────────────────
⊢ P ⇒ Q
end;
rule implication_intro(P Q : Prop)
P := P ⊢ Q
────────────────────────
⊢ P ⇒ Q
end;
lemma contra_id(A : Prop)
⊢ A ⇒ A;
proof
by wip(?goal);
wip;
Hint
contrapositive(A, A) leaves ⊢ ¬A ⇒ ¬A — an identity
implication you close with implication_intro(¬A, ¬A) then P := ¬A ⊢ ¬A;
by P;.
6. Universal proof¶
Goal: prove ∀ x. P(x). Method: let x be arbitrary (a fresh
eigenvariable), then prove P(x). forall_intro hands you that fresh x in
its single premise; because x was arbitrary, proving P(x) proves it for
all.
Exercises
7a. Everything equals itself: prove ∀ x. x = x.
rule forall_intro(T : Sort, P : T → Prop)
x : T ⊢ P(x)
────────────────────────
⊢ ∀ (x : T) st P(x)
end;
axiom refl(T : Sort, x : T) ⊢ x = x;
sort T : Sort;
lemma refl_all
⊢ ∀ (x : T) st x = x;
proof
by wip(?goal);
wip;
Hint
forall_intro(T, _ = _) then x : T ⊢ x = x; carries a fresh x
in; close by refl(T, x).
7b. Instantiate a universal: from ∀ y. P(y) and a point a, get
P(a).
rule forall_elim(T : Sort, P : T → Prop, x : T)
⊢ ∀ (y : T) st P(y)
────────────────────────
⊢ P(x)
end;
sort T : Sort;
lemma at_point(P : T → Prop, a : T, all := ∀ (y : T) st P(y))
⊢ P(a);
proof
by wip(?goal);
wip;
Hint
forall_elim(T, P, a) then ⊢ ∀ (y : T) st P(y); by all;.
7c. From ∀ x. P(x) and ∀ x. Q(x), prove ∀ x. P(x) ∧ Q(x).
rule forall_intro(T : Sort, P : T → Prop)
x : T ⊢ P(x)
────────────────────────
⊢ ∀ (x : T) st P(x)
end;
rule forall_elim(T : Sort, P : T → Prop, x : T)
⊢ ∀ (y : T) st P(y)
────────────────────────
⊢ P(x)
end;
rule and_intro(P Q : Prop)
⊢ P;
⊢ Q
────────────────────────
⊢ P ∧ Q
end;
sort T : Sort;
lemma forall_and(P Q : T → Prop, hp := ∀ (x : T) st P(x), hq := ∀ (x : T) st Q(x))
⊢ ∀ (x : T) st P(x) ∧ Q(x);
proof
by wip(?goal);
wip;
Hint
forall_intro for a fresh x, then and_intro(P(x), Q(x));
get each half by forall_elim on hp / hq at x.
7. Existential proof (a witness)¶
Goal: prove ∃ x. P(x). Method: supply a specific witness a and
prove P(a). exists_intro takes the witness as an argument and leaves you
the single goal P(a).
Exercises
8a. From a proof of P(a), conclude ∃ x. P(x).
rule exists_intro(T : Sort, P : T → Prop, x : T)
⊢ P(x)
────────────────────────
⊢ ∃ (x : T) st P(x)
end;
sort T : Sort;
lemma witnessed(P : T → Prop, a : T, pa := P(a))
⊢ ∃ (x : T) st P(x);
proof
by wip(?goal);
wip;
Hint
exists_intro(T, P, a) then ⊢ P(a); by pa; — you offered a as
the witness, so the leftover goal is P at a.
8b. Something exists that equals a: prove ∃ x. x = a.
rule exists_intro(T : Sort, P : T → Prop, x : T)
⊢ P(x)
────────────────────────
⊢ ∃ (x : T) st P(x)
end;
axiom refl(T : Sort, x : T) ⊢ x = x;
sort T : Sort;
lemma exists_eq(a : T)
⊢ ∃ (x : T) st x = a;
proof
by wip(?goal);
wip;
Hint
Use a itself as the witness: exists_intro(T, λ (z : T) st z = a,
a) then ⊢ a = a; and close by refl(T, a).
8c. Every element is some self-equal thing: prove ∃ z. z = z.
rule exists_intro(T : Sort, P : T → Prop, x : T)
⊢ P(x)
────────────────────────
⊢ ∃ (x : T) st P(x)
end;
axiom refl(T : Sort, x : T) ⊢ x = x;
sort T : Sort;
lemma exists_self(a : T)
⊢ ∃ (z : T) st z = z;
proof
by wip(?goal);
wip;
Hint
Witness with a: exists_intro(T, λ (z : T) st z = z, a) then
⊢ a = a; then by refl(T, a).
8. Existential elimination (use a witness)¶
Goal: conclude Q given ∃ x. P(x). Method: introduce a fresh
witness a with P(a) in hand, and finish the proof using it — but Q
must not mention a, since you don’t get to know which witness you were handed.
exists_elim gives you that fresh x and the hypothesis witness := P(x).
Exercises
9a. Unpack and immediately repack: from ∃ x. P(x), prove ∃ x. P(x).
rule exists_intro(T : Sort, P : T → Prop, x : T)
⊢ P(x)
────────────────────────
⊢ ∃ (x : T) st P(x)
end;
rule exists_elim(T : Sort, P : T → Prop, Q : Prop)
⊢ ∃ (x : T) st P(x);
x : T, witness := P(x) ⊢ Q
────────────────────────
⊢ Q
end;
sort T : Sort;
lemma repack(P : T → Prop, ex := ∃ (x : T) st P(x))
⊢ ∃ (x : T) st P(x);
proof
by wip(?goal);
wip;
Hint
exists_elim(T, P, ∃ (x : T) st P(x)) cases — feed ex for the
existential, then in the witness branch x : T; witness := P(x) rebuild
with exists_intro(T, P, x) then ⊢ P(x); by witness;.
9b. Flip an existential equation: from ∃ x. x = a, prove ∃ y. a = y.
rule exists_intro(T : Sort, P : T → Prop, x : T)
⊢ P(x)
────────────────────────
⊢ ∃ (x : T) st P(x)
end;
rule exists_elim(T : Sort, P : T → Prop, Q : Prop)
⊢ ∃ (x : T) st P(x);
x : T, witness := P(x) ⊢ Q
────────────────────────
⊢ Q
end;
rule symmetry(T : Sort, x y : T)
⊢ x = y
────────────────────────
⊢ y = x
end;
sort T : Sort;
lemma exists_flip(a : T, ex := ∃ (x : T) st x = a)
⊢ ∃ (y : T) st a = y;
proof
by wip(?goal);
wip;
Hint
Open ex with exists_elim; in the witness branch you hold
witness := x = a. Offer x as the new witness
(exists_intro(T, λ (y : T) st a = y, x) then ⊢ a = x;) and flip with
symmetry(T, x, a) then ⊢ x = a; by witness;.
9c. Discharge under a universal: from ∃ x. P(x) and
∀ x. P(x) ⇒ R, prove R.
rule exists_elim(T : Sort, P : T → Prop, Q : Prop)
⊢ ∃ (x : T) st P(x);
x : T, witness := P(x) ⊢ Q
────────────────────────
⊢ Q
end;
rule forall_elim(T : Sort, P : T → Prop, x : T)
⊢ ∀ (y : T) st P(y)
────────────────────────
⊢ P(x)
end;
rule implication_elim(P Q : Prop)
⊢ P ⇒ Q;
⊢ P
────────────────────────
⊢ Q
end;
sort T : Sort;
lemma exists_use(P : T → Prop, R : Prop, ex := ∃ (x : T) st P(x),
use := ∀ (x : T) st P(x) ⇒ R)
⊢ R;
proof
by wip(?goal);
wip;
Hint
In the witness branch (x : T; witness := P(x)) instantiate use
at x with forall_elim to get P(x) ⇒ R, then implication_elim
against witness.
9. Equational reasoning (rewriting)¶
Goal: a goal built from an equation. Method: given a = b, replace a
with b (or b with a) somewhere in the goal — that’s forward /
backward from Reflexivity and rewriting, and refl closes anything of the
form x = x.
Exercises
10a. Close a definitional equation: prove 0 + 0 = 0.
sort Nat : Sort;
op 0 : → Nat;
op s : Nat → Nat;
op + : Nat * Nat → Nat;
axiom add_zero_left(n : Nat) ⊢ 0 + n = n;
axiom add_succ_left(n m : Nat) ⊢ s(n) + m = s(n + m);
lemma zero_plus_zero
⊢ 0 + 0 = 0;
proof
by wip(?goal);
wip;
Hint
0 + 0 = 0 is the axiom add_zero_left at 0: by
add_zero_left(0);.
10b. Rewrite, then reflect: prove n = 0 + n.
sort Nat : Sort;
op 0 : → Nat;
op s : Nat → Nat;
op + : Nat * Nat → Nat;
axiom add_zero_left(n : Nat) ⊢ 0 + n = n;
axiom add_succ_left(n m : Nat) ⊢ s(n) + m = s(n + m);
axiom refl(T : Sort, x : T) ⊢ x = x;
rule forward(T : Sort, a b : T, eq := a = b, P : T → Prop)
⊢ P(b)
────────────────────────
⊢ P(a)
end;
lemma zero_left_flip(n : Nat)
⊢ n = 0 + n;
proof
by wip(?goal);
wip;
Hint
Turn the 0 + n on the right into n with
forward(Nat, 0 + n, n, add_zero_left(n), n = _) then ⊢ n = n; and close
by refl(Nat, n).
10c. Flip an equality: from a = b, prove b = a.
sort Nat : Sort;
op 0 : → Nat;
op s : Nat → Nat;
op + : Nat * Nat → Nat;
axiom add_zero_left(n : Nat) ⊢ 0 + n = n;
axiom add_succ_left(n m : Nat) ⊢ s(n) + m = s(n + m);
rule symmetry(T : Sort, x y : T)
⊢ x = y
────────────────────────
⊢ y = x
end;
lemma flip_eq(a b : Nat, h := a = b)
⊢ b = a;
proof
by wip(?goal);
wip;
Hint
symmetry(Nat, a, b) concludes b = a from ⊢ a = b:
then ⊢ a = b; by h;.
That’s the toolbox. Almost every proof in the standard library — and every
monster in the Dungeon Proof Crawler — is these moves, combined. When a goal
stumps you, ask which shape it has (an implication? a ∀? an equation?) and
reach for the matching technique.