Today, we’ll be proving simple theorems using the Coq proof assistant. Coq uses an ML-like language to describe proofs.
This introduction is an abridged version of Mike Nahas’ tutorial at https://coq.inria.fr/tutorial-nahas.
- Install CoqIDE using
sudo apt install coqide
(or similar) from http://coq.inria.fr/download. - If you want to use this with Emacs, install Proof General mode from http://proofgeneral.inf.ed.ac.uk/.
Coq source files have a .v
extension.
Theorem my_first_proof : (forall A : prop, A -> A).
Proof.
(* This is a comment *)
intros A.
intros proof_of_A.
exact proof_of_A.
Qed.
- Every coq command ends with a full stop.
- The vernacular commands begin with a capital letter; they define the structure of the proof.
- The tactics commands begin with a lowercase letter; they are how you want Coq to prove them.
- The statement we want to prove comes after
my_first_proof
. It says “for all Props A, A implies A”. - A Prop, or proposition, is something that can have a proof. It does not make sense to say a proposition is true or false! It either has a proof or it doesn’t. (Truth values do exist in Coq, but we’ll introduce them later.)
intros
is how you instantiate something you assume to exist.- So, the first three lines of the Proof block instantiates a Prop called A and a proof of that Prop A.
exact
tells Coq that we have reached our goal.
Step through the proof line-by-line. For the first line, we see
1 subgoal, subgoal 1 (ID 1)
============================
forall A : Prop, A -> A
The subgoal, shown beneath the ruled line, is what we need to prove the Theorem.
Next line:
1 subgoal, subgoal 1 (ID 2)
A : Prop
============================
A -> A
The intros
command has popped off the A : Prop
from the forall
. By assuming this arbitrary A exists, the subgoal is simplified to showing that the implication A -> A
holds.
1 subgoal, subgoal 1 (ID 3)
A : Prop
proof_of_A : A
============================
A
Now we assume the condition of A -> A
, namely that the Proposition A is true, i.e. that A has been proved. Now we just need to show A is proved. But we’ve assumed that A is proved, which is exact
-ly what we need to conclude the proof.
Don’t forall
and ->
have the logical meaning? Yes, they do and when you don’t need to define new variables, you can swap freely between them.
- Extend
my_first_proof
to prove the thatforall A : Prop, A -> A -> A
.
- A forward proof creates larger and more complex hypotheses until it reaches the goal. For example,
Theorem forward_small : (forall A B : Prop, A -> (A->B) -> B).
Proof.
intros A.
intros B.
intros proof_of_A.
intros A_implies_B.
pose (proof_of_B := A_implies_B proof_of_A). (* See below *)
exact proof_of_B.
Qed.
- Here, The
pose
tactic assigns the result of ”A_implies_B proof_of_A
”, i.e. it computes a proof of B using the given proof of B, to a new variable. See the how thepose
changes the state:
1 subgoal, subgoal 1 (ID 5)
A, B : Prop
proof_of_A : A
A_implies_B : A -> B
============================
B
After:
1 subgoal, subgoal 1 (ID 7)
A, B : Prop
proof_of_A : A
A_implies_B : A -> B
proof_of_B := A_implies_B proof_of_A : B
============================
B
- A backwards proof breaks the goal into smaller and simpler subgoals.
Theorem backward_small : (forall A B : Prop, A -> (A->B)->B).
Proof.
intros A B.
intros proof_of_A A_implies_B.
refine (A_implies_B _).
exact proof_of_A.
Qed.
- Given a prop, the
refine
tactic changes the subgoal to be the unknowns (indicated by_
) which, combined with the Prop, would give the current subgoal. Here, givenA_implies_B
, Coq changes the subgoal fromB
toA
, since combining a proof of A with the implicationA_implies_B
would let you conclude B. - Note the
refine
may have multiple unknowns, giving multiple goals! Then you will need multipleexact
commands.
- Prove
forall A B C : Prop, A -> (A->B) -> (B->C) -> C
forwards. - Prove the same thing backwards.
- Prove
forall A B C : Prop, A -> (A->B) -> (A->B->C) -> C
forwards. - Prove the same thing backwards.
Coq has two built-in values True
and False
, but these are not Booleans! They are Props: True
is the proposition with a proof I
and False
has no proof. Example:
Theorem True_can_be_proven : True.
exact I.
Qed.
There is a built-in not
operator, with shorthand ~
, to prove a Prop has no proofs.
Theorem False_cannot_be_proven : ~False.
Proof.
unfold not. (* See below *)
intros proof_of_False.
exact proof_of_False.
Qed.
The unfold
command expands the definition of not so that we can get something to use with intros
.
But it’s a bit awkward to talk about the proof of something unprovable. This is a more natural way to write it:
Theorem False_cannot_be_proven__again : ~False.
Proof.
intros proof_of_False.
case proof_of_False.
Qed.
case
creates subgoals for every possible construction of its argument. Here, since False
has no proof, it creates no subgoals, thus completing the proof.
We can use case
to formalise reductio ad absurdum arguments.
Theorem absurd2 : forall A C : Prop, A -> ~ A -> C.
(* There's no C in our hypothesis ---^^^^^^^^,
so we can't use the exact command. *)
Proof.
intros A C.
intros proof_of_A proof_that_A_cannot_be_proven.
unfold not in proof_that_A_cannot_be_proven.
pose (proof_of_False := proof_that_A_cannot_be_proven proof_of_A).
case proof_of_False.
Qed.
“But, Ed,” someone is doubtless saying, “What about Haskell?” To that person, I say here are your damn abstract data types.
Inductive False : Prop := .
Inductive True : Prop :=
I : True.
Inductive bool : Set :=
| true : bool
| false : bool.
Inductive
is so named because you can define types inductively (as we’ll see later).
Coq also provides Definition
and Notation
vernaculars to abbreviate things.
(* This indicates (not A) and A -> False are interchangable *)
Definition not (A:Prop) := A -> False.
(* This creates an operator *)
Notation "~ x" := (not x) : type_scope.
The bool
type is in the library Bool
, which you load with
Require Import Bool.
Two functions it includes are:
Definition eqb (b1 b2:bool) : bool :=
match b1, b2 with
| true, true => true
| true, false => false
| false, true => false
| false, false => true
end.
Definition Is_true (b:bool) :=
match b with
| true => True
| false => False
end.
We see True
is true:
Theorem true_is_True: Is_true true.
Proof.
simpl.
exact I.
Qed.
The tactic simpl
simplifies the subgoal by evaluating the function on its arguments. Here’s another application of simpl
:
Theorem not_eqb_true_false: ~(Is_true (eqb true false)).
Proof.
simpl.
exact False_cannot_be_proven.
Qed.
We want some operations on Booleans. Here’s or:
Inductive or (A B:Prop) : Prop :=
| or_introl : A -> A \/ B
| or_intror : B -> A \/ B
where "A \/ B" := (or A B) : type_scope.
This defines four things:
or
, a function which takes two Props and produces one Prop.or_introl
, a constructor that takes a proof ofA
and returns a proof of(or A B)
.or_intror
, a constructor that takes a proof ofB
and returns a proof of(or A B)
.\/
an operator interchangable withor
.
and
is defined by
Inductive and (A B:Prop) : Prop :=
conj : A -> B -> A /\ B
where "A /\ B" := (and A B) : type_scope.
The destruct
tactic is a little more versatile than case
. It’s recommended for types which have a single constructor (like and
).
Theorem and_commutes__again : (forall A B, A /\ B -> B /\ A).
Proof.
intros A B.
intros A_and_B.
destruct A_and_B as [ proof_of_A proof_of_B].
refine (conj _ _).
exact proof_of_B.
exact proof_of_A.
Qed.
- Prove
~(True -> False)
usingrefine
. - Adapt the proofs in part 1 to use bools instead of Props.
- Prove
(forall a : bool, Is_true (eqb a a))
using ”case a.
”. - Prove
(forall A B : Prop, A -> A \/ B)
. [Hint: you will need to one ofor_introl
andor_intror
.] - Prove
or
commutes. - Prove
(forall A B : Prop, A -> B -> A /\ B)
. - Prove
and
commutes without usingcase
instead ofdestruct
.
We saw earlier that and
and or
Props were not functions, but Inductive types, where we defined instances that can only be defined by calling obscure functions called constructors. bool
is an Inductive type, with constructors true
and false
. true
and false
have no arguments, so they are more like constants than functions.
To manipulate bools, we could use Inductive types like we did for Prop, but it will be easier to use functions. Some built-in ones are:
andb
, with operator&&
.orb
, with operator space.negb
.iff
, with operator<->
.
- If a hypothesis (
intros H.
) contains a function call with all its arguments, then usesimpl in H
to expand it. - The
admit
tactic marks the proof as complete, even if some subgoals are complete. [Exercises completed usingadmit
are not acceptable.]
- Prove
(forall a b, Is_true (orb a b) <-> Is_true a \/ Is_true b)
. [Hint: you’ll need every tactic we’ve seen so far.] - Prove the same thing with
andb
and/\
. - Prove the same thing with
negb
and~
.
That is, what sections of Mike Nahas’ tutorial have I not got round to abridging? What remains are introduction to quantifiers, the natural numbers (nat
) and lists.
- If the subgoal starts with ”
(forall <name> : <type>, ...
” Then use tactic ”intros <name>.
” - If the subgoal starts with ”
<type> -> ...
” Then use tactic ”intros <name>.
” - If the subgoal matches an hypothesis, Then use tactic ”
exact <hyp_name>.
” - If you have an hypothesis ”
<hyp_name>: <type1> -> <type2> -> ... -> <result_type>
” OR an hypothesis ”<hyp_name>: (forall <obj1>:<type1>, (forall <obj2>:<type2>, ... <result_type> ...))
” OR any combination of ”->
” and ”forall", AND you have hypotheses of type "=type1", "=type2
“…, Then use tactic ”pose
” to create something of type ”result_type
”. - If you have subgoal ”
<goal_type>
” AND have hypothesis ”<hyp_name>: <type1> -> <type2> -> ... -> <typeN> -> <goal_type>", Then use tactic "=refine (<hyp_name> _ ...).
” with N underscores. - If your subgoal is ”
True", Then use tactic "=exact I
”. - If your subgoal is ”
~<type>
” or ”~(<term>)
” or ”(not <term>)", Then use tactic "=intros
”. - If any hypothesis is ”
<name> : False", Then use tactic "=case <name>.
” - If the current subgoal contains a function call with all its arguments, Then use the tactic ”
simpl
”. - If there is a hypothesis ”
<name>
” of a created type AND that hypothesis is used in the subgoal, Then you can try the tactic ”case <name>
” - If the subgoal’s top-most term is a created type, Then use ”
refine (<name_of_constructor> _ ...)
”. - If a hypothesis ”
<name>
” is a created type with only one constructor, Then use ”destruct <name> as <arg1> <arg2> ... "
to extract its arguments - If a hypothesis ”
<name>
” contain a function call with all its arguments, Then use the tactic ”simpl in <name>
” - If you have a subgoal that you want to ignore for a while, Then use the tactic “=admit”
- If the current subgoal starts ”
exists <name>, ...
” Then create a witness and use ”refine (ex_intro _ witness _)
” - If you have a hypothesis ”
<name> : <a> = <b>
” AND ”<a>
” in your current subgoal Then use the tactic ”rewrite <name>
” - If you have a hypothesis ”
<name> : <a> = <b>
” AND ”<b>
” in your current subgoal Then use the tactic ”rewrite <- <name>
” - If you have a hypothesis ”
<name> : (<constructor1> ...) = (<constructor2> ...)
” OR ”<name> : <constant1> = <constant2>
”, then use the tactic ”discriminate <name>
” - If there is a hypothesis ”
<name>
” of a created type AND that hypothesis is used in the subgoal, AND the type has a recursive definition Then you can try the tactic ”elim <name>
”