Coq is a tool to help you write formal proofs, that are mechanically verifiable.
This means that once you have proved something in Coq, you have very high assurance
that it is true -- more than what you usually have when doing a pen-and-paper proof.
It can be used in an interactive style, thus we call it an interactive proof assistant.
It is based on a very expressive logic, the Calculus of Inductive Constructions.
What Coq is not.
Coq is not a tool that will automatically prove theorems. So, you can't have Coq solve
your logic or math homework for you, or prove the Goldbach conjecture. Still, it greatly simplifies
the development of formal proofs, by automating some aspects of it.
Why you should learn to use it.
Many reasons: to understand formal mathematical logic better; to verify that a proof
is indeed valid; to develop certified software.
How you could learn to use it.
Sit through this section for the basics.
Follow the standard Coq tutorial. (this presentation is based on it)
Use the reference manual for things not covered there.
For more detailed introductions, look at the Coq'Art book and
Adam Chlipala's book.
Treat it as a programming language: the only proper way to
learn it is to use it!
How to use it.
Coq is installed in the zoo machines, in the directory
/c/cs430/bin
As a development environment for it, you can either use the CoqIDE or ProofGeneral,
which is an Emacs-based environment. Both are installed in the same directory.
Propositional Logic
We will now see how to prove a simple theorem of propositional logic, namely the
S axiom of the Hilbert calculus, in Coq. Its statement is
(P → Q → R) → (P → Q) → P → R
As we said, Coq is an interactive tool. That means that instead of having to write
a development in full and then pass it to Coq (the way we write programs and pass
them to a compiler), we can feed a development little by little to Coq, which will
then help us proceed. Environments like CoqIDE and ProofGeneral help us do that,
and one should use the rest of this tutorial like that.
As a first step, we state the theorem we're trying to prove.
Logical negation is represented as implication of False; so notA is equivalent
to A → False. Still, the treatment of logical negation in Coq is uncommon,
in that in its underlying logic the law of the excluded middle
does not hold, unless we explicitly include it. We can still prove the law of
noncontradiction:
We can summarize some of the tactics we can use for propositional logic in the following table:
As a goal
As a hypothesis
→
intros
(various)
∧
split
destruct
∨
left or right
destruct
True
trivial
--
False
--
destruct
Predicate logic
Universally quantified goals can be proven by introducing a new
free variable through the intros tactic, while such hypotheses
can be used by specifying instantiations for the quantified variables
(implicitly or explicitly) when using tactics like apply and
destruct. Existentially quantified goals can be proven by selecting a witness
for which they hold; existentially quantified hypotheses can be
opened up so as to give a name to their contained witness for the
rest of the proof.
We can thus augment the previous tactics table with the following:
As a goal
As a hypothesis
∀
intros
... with (x := ...)
∃
exists
destruct
=
trivial when a = a
rewrite
Inductive definitions
In Coq we specify the objects that we reason about by defining
inductively how such objects can be constructed. For example, if
we want to be able to reason about natural numbers, we would give
the following definition:
O is the constructor that corresponds to zero; S is the constructor
that corresponds to the successor function.
Immediately from the previous definition, we get the principle of
induction over natural numbers:
Printnat_ind.
nat_ind : ∀ P : nat → Prop, PO →
(∀ n : nat, Pn → P (Sn)) →
∀ n : nat, Pn
In fact, inductive definitions play a much more central role in Coq
than just being able to inductively define sets like natural numbers.
Predicates like the ≤-relation for natural numbers are also be
inductively defined.
What is interesting is that it is easier to prove this by
performing induction on the derivation of n ≤ m instead
of natural number induction on n or m.
The induction principle that we get from the definition of ≤ is the
following: (∀ n.Pnn) → (∀ n, m.Pnm → Pn (Sm)) →
(∀ n, m.n ≤ m → Pnm)
By instantiating P to funab ⇒ Sa ≤ Sb we get: (∀ n.Sn ≤ Sn) → (∀ n, m.Sn ≤ Sm → Sn ≤ S (Sm)) →
(∀ n, m.n ≤ m → Sn ≤ Sm)
As a last example, let's see how recursive functions are handled.
We can use the Fixpoint command to define such functions, e.g.
the addition of natural numbers:
Fixpointplus (ab : nat) : nat := matchawith O ⇒ b
|S a ⇒ S (plusab) end.
These functions need to be terminating, in order to ensure
logical consistency.
The simpl tactic can be used to perform evaluation of a function.
Declarative proof style
The proof scripts we've been writing are very hard to read,
and also perform too many low-level steps. There are a number
of ways we can mitigate these problems, and arrive at scripts
that are better at conveying the essential ideas of proofs,
and easier to write. One way to do this is to use Coq's
new declarative proof mode, where we specify intermediate steps
rather than the tactics required to reach them; another
way is through the careful use of automation. Here we'll
briefly see the declarative style.
Let's revisit a theorem we proved earlier and see its declarative proof.
Theoremlatin_square_property_decl : ∀ ab : G, ∃ x : G, a × x = b. proof. leta : G, b : G. take (inva × b). haveH1:(a × (inva × b) = (a × inva) × b) byassociativity. haveH2:(a × inva = e) byinverse. have (a × (inva × b) = e × b) byH1, H2.
~= bbyidentity. hencethesis. endproof. Qed.
The basic way of doing proofs in the declarative style is the following:
We use let and assume instead of intros.
We use have to state one fact and explicitly provide justification
for it by listing the hypotheses we use. then is similar
but includes the previous fact in the justifications.
We use thus to show (part of) the thesis, including justification
if needed. hence includes the previous fact.
When doing induction, we need to explicitly specify the different
cases. Dependent induction is still not supported though...