Coq in A Hurry
Coq in A Hurry
Yves Bertot
These notes provide a quick introduction to the Coq system and show how it can be
used to define logical concepts and functions and reason about them. It is designed as
a tutorial, so that readers can quickly start their own experiments, learning only a few
of the capabilities of the system. A much more comprehensive study is provided in [1],
which also provides an extensive collection of exercises to train on.
Check True.
True : Prop
Check False.
False : Prop
Check 3.
3 : nat
Check (3+4).
3 + 4 : nat
Check (3=5).
3=5 : Prop
1
Check (3,4).
(3,4) : nat * nat
Check ((3=5)/\True).
3 = 5 /\ True : Prop
2
Please note that some notations are overloaded. For instance, the * sign is used both
to represent conventional multiplication on numbers and the cartesian product on types.
One can find the function hidden behind a notation by using the Locate command.
The conditions for terms to be well-formed have two origins: first, the syntax must
be respected (parentheses must be balanced, binary operators must have two arguments,
etc); second, expressions must respect a type discipline. The Check command not only
checks that expressions are well-formed but it also gives the type of expressions. For
instance we can use the Check command to verify progressively that some expressions
are well-formed.
Check True.
True : Prop
Check False.
False : Prop
Check and.
and : Prop -> Prop -> Prop
In the last example, and is a function that expects an argument of type Prop and returns
a function of type Prop -> Prop. It can therefore be applied to True, which has the
right type. But the function we obtain expects another argument of type Prop and it can
be applied to the argument False. The notation
a -> b -> c
a -> (b -> c)
3
Eval compute in
let f := fun x => (x * 3, x) in f 3.
= (9, 3) : nat * nat
After executing this command, the function f is not defined, it was just defined tem-
porarily inside the expression that was evaluated. Here f can be used only inside the
expression f 3.
Exercise on functions (exercise 2.5 from [1]) Write a function that takes five argu-
ments and returns their sum, use Check to verify that your description is well-
formed, use Eval to force its computation on a sample of values. solutions to all
exercises are given at the end of these notes.
2 Programming in Coq
In the Coq system, programs are usually represented by functions. Simple programs can
be executed in the Coq system itself, more complex Coq programs can be transformed
into programs in more conventional languages and executed outside Coq.
After executing one of these commands, the function can be used in other commands:
Check example1.
example1 : nat -> nat
Sometimes, you may want to forget a definition that you just made. This is done using
the Reset command, giving as argument the name you just defined. Moreover, if you
want to see the definition of an object, you can use the Print command. When using
a specialized environment for developing your proofs, like Coqide or Proof-general, you
simply need to move the point of execution from one place of the document to another,
using arrows that are provided at the top of the working window.
4
2.2 boolean conditional expressions
One of the basic pre-defined types of Coq is the type of boolean values. This type contains
two values true and false. However, to use boolean expressions fully, one needs to
load a library, called Bool, this brings the definitions of boolean conjunction, boolean
disjunction, boolean negation, etc.
Boolean values can be tested with an if ...then ...else ... construct, as in the
following example:
Knowing what functions are provided for this datatype can be done using the com-
mands Search or SearchAbout. For instance, here is the result of the Search command.
Search bool.
eqb: bool -> bool -> bool
ifb: bool -> bool -> bool -> bool
true: bool
false: bool
andb: bool -> bool -> bool
orb: bool -> bool -> bool
implb: bool -> bool -> bool
xorb: bool -> bool -> bool
negb: bool -> bool
5
The expression match ...with ...end is called a pattern-matching construct. The value
of the expression depends on the pattern that n satisfies: if n fits in the pattern 0, then
the value of the expression is true. If n fits in the pattern S p, then the value is false. In
the pattern S p, the symbol S is used to describe the concept of successor, so intuitively,
if n fits the pattern S p, this means that we detected that n is the successor of some
number. The occurrence of p in this pattern introduces a local variable, which receives
the value of which n is the successor, and p can be used in the right hand side of the =>
arrow sign.
Pattern-matching is used frequently to compute on natural numbers. For instance,
the Coq system contains a function named pred, which maps any successor number larger
than 0 to its predecessor and which maps 0 to 0.
Print pred.
pred = fun n : nat =>
match n with | 0 => n | S u => u end
: nat -> nat
The definition of pred uses the pattern-matching construct to express that any number
of the form S u will be mapped to u and any number of the form 0 will be mapped to
itself.
Another feature that is used frequently to compute on natural numbers is recursivity.
When defining a function using pattern matching, we are allowed to use the function that
we are defining on the predecessor of the argument. Coq is very sensitive to recursive
definitions, and we have to use a specific keyword to express that recursion will be used
in the definition. This keyword is called Fixpoint. Here is an example of a recursive
function that adds the first natural numbers:
Fixpoint sum_n n :=
match n with
0 => 0
| S p => p + sum_n p
end.
Fixpoint rec_bad n :=
match n with 0 => 0 | S p => rec_bad (S p) end.
Error:
6
Recursive definition of rec_bad is ill-formed.
In environment
rec_bad : nat -> nat
n : nat
p : nat
Recursive call to rec_bad has principal argument equal to
"S p"
instead of p.
Recursive functions may have several arguments. In this case, the constraint of struc-
tural recursion must be satisfied by one of the argument positions. For instance, the
following function takes an intermediate sum as argument and adds the first natural
numbers to this extra argument. The constraint of structural recursion is satisfied for the
first argument: in the recursive call, p is the argument in first position and it is obtained
by pattern-matching from the initial argument in first position, n:
Fixpoint sum_n2 n s :=
match n with
0 => s
| S p => sum_n2 p (p + s)
end.
We can also define recursive functions that return values in other datatypes, like the type
of boolean values, as in the following function that computes whether its argument is
even:
Fixpoint evenb n :=
match n with
0 => true
| 1 => false
| S (S p) => evenb p
end.
This function also shows new possibilities: we can use deep pattern-matching and have
more than two cases in the pattern-matching construct. However, Coq will still check
that all cases are covered, and recursive calls are still allowed only on variables obtained
through pattern-matching.
To group several elements in the same list, we simply need to write them together,
separated by two columns ’::’, and to add ’::nil’ at the end.
7
Check 1::2::3::nil.
1 :: 2 :: 3 :: nil
: list nat
The notation ’::’ is actually used to add an element at the head of another list. Thus, nil
represents an empty list, 3::nil represents a list with only the element 3, and 2::3::nil
represents a list with 2 on the head of a list with only the element 3.
The nil expression has a special status in the Coq system, because it needs to know
in which context it is used. In a list containing some elements, the nil expression is
understood to represent an empty list for the type of these elements. However, the Coq
system has trouble handling the nil expression when it is in complete isolation.
Check nil.
Error: Cannot infer the implicit parameter A of nil.
To circumvent this problem, we can tell Coq that the nil expression is to be used in the
context where a specific list type is expected.
Check (nil : list nat).
nil:list nat
: list nat
There are a few pre-defined functions on lists. For instance, there is a list concate-
nation function, named app, and usually noted with ++, and there is a list processing
function map that takes as inputs a function and list of values, and returns the list of
results of this function applied to these values.
Eval compute in map (fun x => x + 3) (1::3::2::nil).
= 4::6::5::nil : list nat
Eval compute in
let l := (1::2::3::nil) in l ++ map (fun x => x + 3) l.
= 1::2::3::4::5::6::nil : list nat
Exercise on lists, map, and app Define a function that takes as input a number n and
returns a list with n elements, from 0 to n − 1.
To access the elements of a list, we can use the same pattern-matching construct as
the one we used for natural numbers, with a small change. Now, a list can satisfy one of
two cases: either it is the empty list, or it has an element at the head of another list. In
the second case, the pattern-matching construct makes it possible to give names to the
first element and the rest of the list. See in the following example, which returns true if
the argument list has at least one element and this element is even:
Definition head_evb l :=
match l with nil => false | a::tl => evenb a end.
8
We can also use recursion on lists, with the same kind of structural recursion constraint
as for recursion on natural numbers. Recursive calls can only be made on direct sublists.
For instance, the following function adds up all the elements present in a list:
Fixpoint sum_list l :=
match l with nil => 0 | n::tl => n + sum_list tl end.
As an example, we can write a simple program that sorts a list of natural numbers,
knowing that the Coq system already provides a function called leb which compares two
natural numbers and returns a boolean value.
Fixpoint insert n l :=
match l with
nil => n::nil
| a::tl => if leb n a then n::l else a::insert n tl
end.
Fixpoint sort l :=
match l with
nil => nil
| a::tl => insert a (sort tl)
end.
Exercise on sorting Define a function that takes a list as input and returns true when
it has less than 2 elements or when the first element is smaller than or equal to
the second one. Then define a function that takes a list as input and returns true
exactly when this list is sorted (Hint: when the list has at least two elements, the
first element must be smaller than the second element and the tail must be sorted).
Exercise on counting Knowing that the Coq system provides a function beq nat to
compare two natural numbers, define a function count list that takes a natural
number and a list and returns the number of times the natural number occurs in
the list.
9
3.1 Finding existing proofs
One can find already existing proofs of facts by using the Search command. Its argument
should always be an identifier.
Search True.
I : True
Search le.
between_le: forall (P : nat -> Prop) (k l : nat),
between P k l -> k <= l
exists_le_S:
forall (Q : nat -> Prop) (k l : nat),
exists_between Q k l -> S k <= l
...
plus_le_reg_l: forall n m p : nat, p + n <= p + m -> n <= m
plus_le_compat_l: forall n m p : nat, n <= m -> p + n <= p + m
plus_le_compat_r: forall n m p : nat, n <= m -> n + p <= m + p
le_plus_l: forall n m : nat, n <= n + m
...
le_n : forall n : nat, n <= n
le_S : forall n m : nat, n <= m -> n <= S m
The theorem le S uses a function S, this function maps any natural number x to its suc-
cessor x + 1. Actually, the notation 3 is only a notation for S (S (S O)). The statement
of le S reads as : for every n and m, numbers of type nat, if n is less than or equal to m,
then n is less than or equal to the successor of m. The arrow, ->, which was already used
to represent function types is used here to represent implication between propositions.
In practice, the ambiguity between the two uses of -> is never a problem.
The command SearchPattern takes a pattern as argument, where some of the ar-
guments of a predicate can be replaced by incomplete expressions (we use a special
anonymous variable to represent holes in incomplete expressions).
SearchPattern (_ + _ <= _ + _).
plus_le_compat_l: forall n m p: nat, n <= m -> p + n <= p + m
plus_le_compat_r: forall n m p: nat, n <= m -> n + p <= m + p
plus_le_compat: forall n m p q: nat, n <= m -> p <= q -> n + p <= m + q
The command SearchRewrite is similar, but it only looks for rewriting theorems, that
is, theorems where the proved predicate is an equality. The listed theorems are those for
which the pattern fits one of the members of the equality in the conclusion.
SearchRewrite (_ + (_ - _)).
le_plus_minus: forall n m : nat, n <= m -> m = n + (m - n)
le_plus_minus_r: forall n m : nat, n <= m -> n + (m - n) = m
The command SearchAbout makes it possible to find all theorems that are related to
a given symbol. For instance, we used a function leb when describing a sorting program.
We can look for all theorems related to this function.
10
SearchAbout leb.
leb_correct: forall m n : nat, m <= n -> leb m n = true
leb_complete: forall m n : nat, leb m n = true -> m <= n
leb_correct_conv: forall m n : nat, m < n -> leb n m = false
leb_complete_conv: forall m n : nat, leb n m = false -> m < n
leb_compare: forall n m : nat, leb n m = true <-> nat_compare n m <> Gt
2. the Coq system displays the formula as a formula to be proved, possibly giving a
context of local facts that can be used for this proof (the context is displayed above
a horizontal line written =====, the goal is displayed under the horizontal line),
3. the user enters a command to decompose the goal into simpler ones,
4. the Coq system displays a list of formulas that still need to be proved,
5. back to step 3.
The commands used at step 3 are called tactics. Some of these tactics actually decrease
the number of goals. When there are no more goals the proof is complete, it needs to
be saved, this is performed when the user sends the command Qed. The effect of this
command is to save a new theorem whose name was given at step 1. Here is an example:
Lemma example2 : forall a b:Prop, a /\ b -> b /\ a.
1 subgoal
============================
forall a b : Prop, a /\ b -> b /\ a
Proof.
intros a b H.
1 subgoal
a : Prop
b : Prop
H : a /\ b
============================
b /\ a
split.
2 subgoals
...
11
H : a /\ b
============================
b
subgoal 2 is:
a
exact H2.
1 subgoal
...
H : a /\ b
============================
a
intuition.
Proof completed.
Qed.
intros a b H.
split.
destruct H as [H1 H2]
exact H2.
intuition.
example2 is defined
The theorem that is defined is called example2, it can later be used in other tactics.
This proof uses several steps to describe elementary reasoning steps. There is a large
collection of tactics in the Coq system, each of which is adapted to a shape of goal. Each
goal actually has two parts, a context and a conclusion. The elements of the context
usually have the form a : type or H : formula , and we call them hypotheses. They
represent facts that we can temporarily assume to hold.
The tactic destruct H as [H1 H2] was adapted because the hypothesis H was a
proof of a conjunction of two propositions. The effect of the tactic was to add two new
hypotheses in the context, named H1 and H2. It is worthwhile remembering a collection
of tactics for the basic logical connectives.
We list basic tactics in the following table, inspired from the table in [1] (p. 130).
To use this table, you should remember that each connective can appear either in an
hypothesis of the context (and we assume this hypothesis is named H) or in the conclusion
of the goal. The tactic to be used in each case is not the same. When working on
hypothesis, you should use the tactics listed in rows starting with “Hypothesis”. For
12
instance, the first step of the proof of example2 works on a universal quantification,
in the conclusion, hence the tactic that is used is intros. This intros tactic has three
arguments a, b, and H, so it actually processes the first three connectives of the conclusion:
two quantifications and an implication. The second step works on a conjunction /\ and
in the conclusion, hence the tactic is split. The third step works on a conjunction and
in the hypothesis H, hence the tactic is destruct H as [H1 H2]. The fourth step is
not referenced in the table: the tactic exact H2 simply expresses that we want to prove
a statement that is present in the context, in the hypothesis H2. A tactic assumption
could also have been used to express that we want Coq to look for one hypothesis whose
statement is the same as the conclusion. The fifth step performs a less elementary step:
it calls an automatic tactic called intuition.
⇒ ∀ ∧
Hypothesis H apply H apply H elim H
case H
destruct H as [H1 H2]
conclusion intros H intros H split
¬ ∃ ∨
Hypothesis H elim H elim H elim H
case H case H case H
destruct H as [x H1] destruct H as [H1 | H2]
conclusion intros H exists v left or
right
= False
Hypothesis H rewrite H elim H
rewrite <- H case H
conclusion reflexivity
ring
When using the tactic elim or case, this usually creates new facts that are placed in the
conclusion of resulting goals as premises of newly created implications. These premises
must then be introduced in the context using the intros tactic. A quicker tactic does
the two operations at once, this tactic is called destruct.
All tactics that work on hypotheses and take hypothesis names as arguments, like
destruct, apply, or rewrite, can also take theorem names as arguments. Thus, every
theorem you prove and save can later be used in other proofs.
13
intros A B H.
...
A : Prop
B : Prop
H : A \/ B
============================
B \/ A
Intuitively, this reasoning step corresponds to the sentence “let’s fix two propositions A
and B and let’s assume that A \/ B holds, now we only have to prove B \/ A.” After this,
we need to think a little about how to attach the problem. To prove A \/ B, we could
choose to work on the conclusion. This would correspond to choosing to prove either A
or B (using the tactics left and right). However, if we only know that A \/ B holds,
we cannot prove A, because A \/ B could be a consequence of the fact that B holds, but
not A. Thus, working on the conclusion directly does not lead to a feasible proof. On the
other hand, we can choose to work on the hypothesis. If A \/ B holds, there actually are
two cases: either A holds or B holds, and we can ask to see these two cases independently.
This is done by using the tactic destruct.
Subgoal 2 is
B \/ A
Two goals are generated, and the hypothesis H1 in the first goal tells us that we are in the
case where we know that A holds. We are now in a situation where we can work directly
on the conclusion, choosing to prove the right-hand part A, because we know that in this
case, it holds.
right; assumption.
...
H1 : B
============================
B \/ A
The semi-column ‘;’ is used to compose several tactics together. More precisely, the tactic
on the right hand side is applied to all the goals produced by the tacti on the left hand
side. Here, the tactic right transforms the goal’s conclusion B \/ A into A (it states that
we want to prove the right-hand part of the ‘or’ statement); then the tactic assumption
proves this by finding an assumption with A as statement, namely H1. Because the tactic
right; assumption solves the first goal, the Coq system answers by displaying only the
second goal, which is now the only one. We can solve this goal in a similar manner by
choosing to prove the left-hand side.
14
left; assumption.
Proof completed.
Qed.
As usual we finish our proof by writing Qed. This saves the proof as a theorem that we
can use later.
forall x1 ... xk, A1 x1 ... xk -> A2 x1 ... xk -> ... -> An x1 ... xk ->
C x1 ... xk
======================
C a1 ... ak
Then the tactic apply th replaces the current goal with n goals, whose statements are A1
a1 ... ak, . . . An a1 ... ak. This works well when all the variables that universally
quantified (here x1 . . . xn) appear in the goal conclusion. By matching the theorem’s
conclusion with the goal conclusion, the Coq system is able to infer that x1 has to be a1,
x2 has to be a2, and so on.
When a variable does not occur in the theorem’s conclusion, the Coq system is not
able to infer the value this variable must take, but the user can specify its value using a
directive with.
Let’s illustrate this with two examples. In the first example, we only illustrate the
simple use of apply. We use two theorems about comparisons between natural numbers.
Check le_n.
le_n : forall n : nat, n <= n
Check le_S.
le_S : forall n m : nat, n <= m -> n <= S m
15
apply le_S.
============================
3 <= 4
In a second step, we can apply again the same theorem. This time the Coq system figures
out that n has to be 3 and m has to be 3.
apply le_S.
============================
3 <= 3
Now we get a goal that is an instance of le n. This is again checked by the apply tactic,
but this time there is no new suboal generated and we can save the proof.
apply le_n.
Proof completed.
Qed.
In a second example, let’s illustrate the case where some variable that is universally
quantified in the hypothesis or theorem statement does not appear in the theorem’s
conclusion. A typical theorem that exhibits this characteristic is a transitivity theorem,
like the transitivity theorem for the order “less than or equal to” on natural numbers.
Check le_trans.
le_trans : forall n m p : nat, n <= m -> m <= p -> n <= p
x : nat
y : nat
x10 : x <= 10
y10 : 10 <= y
============================
x <= y
If we try to use le trans with the apply tactic as before, we get an error message.
apply le_trans.
Error: Unable to find an instance for the variable m.
The variable m does not occur in the conclusion of the theorem. Matching the conclusions
of the theorem and the goals does not help finding the value for this variable. The user
can give the value by hand:
apply le_trans with (m := 10).
2 subgoals
============================
x <= 10
16
subgoal 2 is:
10 <= y
assumption.
assumption.
Qed.
At this point we would like to use a theorem expressing distributivity, let’s search for this
theorem using the SearchRewrite command.
SearchRewrite (_ * (_ + _)).
mult_plus_distr_l: forall n m p : nat, n * (m + p) = n * m + n * p
rewrite mult_plus_distr_l.
============================
(x + y) * x + (x + y) * y = x * x + 2 * x * y + y * y
We can observe that the Coq found on its own that the universally quantified variables
had to be instanciated in the following manner: n had to be (x + y), m had to be x, and
p had to be y. We can now use a similar distributivity theorem where multiplication is
on the right-hand side to progress in our proof.
rewrite mult_plus_distr_r.
============================
x * x + y * x + (x + y) * y = x * x + 2 * x * y + y * y
In the last step, there were two possible choices of instanciation, one where p had to be x
and one where p had to be y. The Coq system chose the former, but we could have been
more directive and have written rewrite mult plus distr r with (p:=x). In the next
steps, we look for theorems that make it possible to reshape the left-hand side of the
equality, and then we work on the right-hand side.
17
rewrite mult_plus_distr_r.
============================
x * x + y * x + (x * y + y * y) = x * x + 2 * x * y + y * y
SearchRewrite (_ + (_ + _)).
plus_assoc: forall n m p : nat, n + (m + p) = n + m + p
plus_permute: forall n m p : nat, n + (m + p) = m + (n + p)
plus_assoc_reverse: forall n m p : nat, n + m + p = n + (m + p)
plus_permute_2_in_4:
forall n m p q : nat, n + m + (p + q) = n + p + (m + q)
rewrite plus_assoc.
============================
x * x + y * x + x * y + y * y = x * x + 2 * x * y + y * y
At this point, we would like to rewrite with a theorem from right to left, this is possible
using the <- modifier.
Now we want to use commutativity for multiplication. We can find the relevant theorem
using the SearchPattern command.
SearchRewrite (S _ * _).
mult_1_l: forall n : nat, 1 * n = n
mult_succ_l: forall n m : nat, S n * m = n * m + m
...
In our goal, we have two occurrences of the expression (x * y) and we want to rewrite
only one of them with mult 1 l from right to left. This is possible, using a tactic called
pattern to limit the place where rewriting occurs, stating that we want only the first
occurrence to be rewritten.
18
rewrite <- mult_succ_l.
============================
x * x + 2 * (x * y) + y * y = x * x + 2 * x * y + y * y
SearchRewrite (_ * ( _ * _)).
mult_assoc_reverse: forall n m p : nat, n * m * p = n * (m * p)
mult_assoc: forall n m p : nat, n * (m * p) = n * m * p
rewrite mult_assoc.
============================
x * x + 2 * x * y + y * y = x * x + 2 * x * y + y * y
We can now conclude using reflexivity as prescribed by the table page 13.
reflexivity.
Proof completed.
Qed.
Sometimes rewriting theorems have premises stating that the equality is valid only under
some condition. When rewriting is performed with using theorems, the corresponding
instance of premises is added as extra goals to be proved later.
19
Lemma omega_example :
forall f x y, 0 < x -> 0 < f x -> 3 * f x <= 2 * y -> f x <= y.
intros; omega.
Qed.
At this point, you should be able to perform the following two exercises.
Exercise on logical connectives (Exercise 5.6 from [1]) Prove the following theorems:
Two benefits can be taken from this exercise. In a first step you should try using
only the basic tactics given in the table page 13. In a second step, you can verify
which of these statements are directly solved by the tactic intuition.
• discriminate will be usable when one hypothesis asserts that 0 = S ... or true
= false.
• injection will be usable when one hypothesis has the form S x = S y to deduce
x = y.
Let us look at an example proof. We want to show that the sum of the n first natural
numbers satisfies a well-known polynomial formula.
20
Lemma sum_n_p : forall n, 2 * sum_n n + n = n * n.
induction n.
============================
2 * sum_n 0 + 0 = 0 * 0
subgoal 2 is:
2 * sum_n (S n) + S n = S n * S n
In the two generated goals, the first one has n replaced by 0, the second one has n replaced
by S n with a hidden hypothesis corresponding the statement being already true for n.
The first goal is very easy, because the definitions of sum n, addition and multiplication
make that both members of the equality are 0. The tactic reflexivity solves this goal.
reflexivity.
After we send this tactic the second goal remains alone and is repeated in full. We can
now see the induction hypothesis.
n : nat
IHn : 2 * sum_n n + n = n * n
============================
2 * sum_n (S n) + S n = S n * S n
In this goal, we need to think a little. We can expand S n * S n into a formula where
n * n occurs, and then rewrite with IHn, from right to left. We use the assert tactic
to describe a intermediary equality between S n * S n the new value we want to use
instead and the ring tactic to show that this replacement is valid.
ring.
n : nat
IHn : 2 * sum_n n + n = n * n
SnSn : S n * S n = n * n + 2 * n + 1
============================
2 * sum_n (S n) + S n = n * n + 2 * n + 1
rewrite SnSn.
n : nat
IHn : 2 * sum_n n + n = n * n
SnSn : S n * S n = n * n + 2 * n + 1
============================
2 * sum_n (S n) + S n = n * n + 2 * n + 1
We attack this goal by rewriting with the induction hypothesis, this replaces occurrences
of the induction hypothesis right-hand side with by the left-hand side.
21
rewrite <- IHn.
============================
2 * sum_n (S n) + S n = 2 * sum_n + n + 2 * n + 1
The next step is to make sum n (S n) compute symbolically, so that its value is expressed
usin sum n n according to the definition. The tactic we use also forces the symbolic
computation of multiplications.
simpl.
============================
n + sum_n n + (n + sum_n n + 0) + S n =
sum_n + (sum_n n + 0) + (n + (n + 0)) + 1
The next step is to make the ring tactic recognize that this equation is a consequence of
associativity and commutativity of addition.
ring.
Qed.
When the recursive function has several levels of pattern matching, it is worthwhile
proving statements about several successive numbers, as in the following proof about the
function evenb.
Lemma evenb_p : forall n, evenb n = true -> exists x, n = 2 * x.
assert (Main: forall n, (evenb n = true -> exists x, n = 2 * x) /\
(evenb (S n) = true -> exists x, S n = 2 * x)).
At this point, we choose to prove a stronger statement, stating that the property we wish
to prove is satisfied by both n and S n. We then proceed by a regular induction. Proving
stronger statements by induction is a very common feature in proofs about recursive
functions.
induction n.
============================
(evenb 0 = true -> exists x : nat, 0 = 2 * x) /\
(evenb 1 = true -> exists x : nat, 1 = 2 * x)
subgoal 2 is:
(evenb (S n) = true -> exists x : nat, S n = 2 * x) /\
(evenb (S (S n)) = true -> exists x : nat, S (S n) = 2 * x)
subgoal 3 is:
forall n : nat, evenb n = true -> exists x : nat, n = 2 * x
The first goal has two parts, and for the first part, we need to work on an existential
statement that occurs in the conclusion. Refering to the table of tactic on page 13, we see
that we need to provide the existing value, using the exists tactic. Here the existing value
for x is 0, since 0 = 2 * 0 is easy to prove. The second part is different: it is not possible
to choose a value for x, but the hypothesis evenb 1 = true is inconsistent, because evenb
1 actually computes to false and the equality false = true is not possible. When such
an inconsistent case is reached we can use the discriminate tactic.
22
split.
exists 0; ring.
simpl; intros H; discriminate H.
We do not detail the rest of the proof, but we give it for readers to try on their own
computer.
split.
destruct IHn as [_ IHn’]; exact IHn’.
simpl; intros H; destruct IHn as [IHn’ _].
assert (H’ : exists x, n = 2 * x).
apply IHn’; exact H.
destruct H’ as [x q]; exists (x + 1); rewrite q; ring.
Once the Main hypothesis is proved, we can come back to our initial statement. At
this point, the tactic firstorder is strong enough to finish the proof, but for didactic
purposes, we will show another approach.
Main : forall n : nat,
(evenb n = true -> exists x : nat, n = 2 * x) /\
(evenb (S n) = true -> exists x : nat, S n = 2 * x)
============================
forall n : nat, evenb n = true -> exists x : nat, n = 2 * x
intros n ev.
...
n : nat
ev : evenb n = true
============================
exists x : nat, n = 2 * x
Here we can use a facility of the Coq system that we haven’t mentioned yet: when the
statement of a theorem is a universal quantification or an implication, we can use this
theorem as if it was a function: when applied to an expression it gives a new theorem
whose statement is instantiated on this expression. In our case, we can use the hypothesis
Main and instantiate it on n.
destruct (Main n) as [H _]; apply H; exact ev.
Qed.
23
Remember that you may use a lemma you just proved when proving a new exercise.
Exercise on the sum of odd numbers The sum of the first n odd natural numbers
is defined with the following function:
24
The existing theorem beq nat refl is useful for us, we use it with a rewrite tactic.
This goal is easily solved by reflexivity. The next goal is the step case of the proof by
induction on lists.
reflexivity.
...
IHl : count n (insert n l) = 1 + count n l
============================
count n (insert n (a :: l)) = 1 + count n (a :: l)
simpl.
============================
count n (if leb n a then n :: a :: l else a :: insert n l) =
S (if beq_nat n a then S (count n l) else count n l)
We first need to reason by cases on the expression leb n a and then by cases on the
expression beq nat n a. We use the tactic case (leb n a) for the first reasoning step.
It produces two goals, one where leb n a is replaced by true, and one where it is replaced
by false.
subgoal 2 is:
count n (a :: insert n l) =
S (if beq_nat n a then S (count n l) else count n l)
We can now simplify the expression. We see that beq nat appears again and we can
re-use the theorem beq nat refl
simpl.
...
============================
(if beq_nat n n
then S (if beq_nat n a then S (count n l) else count n l)
else if beq_nat n a then S (count n l) else count n l) =
S (if beq_nat n a then S (count n l) else count n l)
25
...
============================
S (if beq_nat n a then S (count n l) else count n l) =
S (if beq_nat n a then S (count n l) else count n l)
reflexivity.
IHl : count n (insert n l) = 1 + count n l
============================
count n (a :: insert n l) =
S (if beq_nat n a then S (count n l) else count n l)
simpl.
IHl : count n (insert n l) = 1 + count n l
============================
(if beq_nat n a then S (count n (insert n l))
else count n (insert n l)) =
S (if beq_nat n a then S (count n l) else count n l)
subgoal 2 is:
count n (insert n l) = S (count n l)
In the two goals that are produced, rewriting with the hypothesis IHl will obviously lead
to equalities where both sides are the same. We conclude the proof with the following
two commands.
26
6.1 Defining inductive types
Here is an example of an inductive type definition:
This defines a new type bin, whose type is Type, and expresses that the data in this
type respects two cases. In the first case, there is no field and the data is noted L, in the
second case there are two fields of type bin and the data can be written as N t1 t2 . In
other words, elements of the type bin can be obtained in two different ways, either by
taking the constant L or by applying N to two objects of type bin.
Check N L (N L L).
N L (N L L) : bin
The type bin contains binary trees with no extra information carried in the internal
nodes or leaves.
Exercise on datatype definition Define a datatype where there are three cases: a
constant, a case where there are three fields, where the first field is a number and
the next two fields are in the datatype being defined, and a case with four fields,
where the first field is a boolean value and the three other fields are in the datatype
being defined.
27
Fixpoint flatten (t:bin) : bin :=
match t with
L => L | N t1 t2 => flatten_aux t1 (flatten t2)
end.
Lemma example7_size :
forall t, example7 t = false -> size t = 3.
intros t; destruct t.
2 subgoals
============================
example7 L = false -> size L = 3
subgoal 2 is:
example7 (N t1 t2) = false -> size (N t1 t2) = 3
The tactic destruct t actually observes the various possible cases for t according to the
inductive type definition. The term t can only be either obtained by L, or obtained by N
applied to two other trees t1 and t2. This is the reason why there are two subgoals.
We know the value that example7 and size should take for the tree L. We can direct
the Coq system to compute it:
simpl.
2 subgoals
============================
true = false -> 1 = 3
After computation, we discover that assuming that the tree is L and that the value of
example7 for this tree is false leads to an inconsistency, because example7 L computes
to true while it is assumed to be false. We can use the following tactic to exploit this
kind of inconsistency:
28
intros H.
H : true = false
============================
1 = 3
discriminate H.
1 subgoal
...
============================
example7 (N t1 t2) = false -> size (N t1 t2) = 3
The answer shows that the first goal was solved. The tactic discriminate H can be used
whenever the hypothesis H is an assumption that asserts that two different constructors
of an inductive type return equal values. Such an assumption is inconsistent and the
tactic directly exploits this inconsistency to express that the case described in this goal
can never happen.
Another important property of constructors of inductive types is that they are injec-
tive. The tactic to exploit this fact called injection. We illustrate it later in section 6.6.
For the second goal we still must do a case analysis on the values of t1 and t2, we do
not detail the proof but it can be completed with the following sequence of tactics.
destruct t1.
destruct t2.
3 subgoals
============================
example7 (N L L) = false -> size (N L L) = 3
subgoal 2 is:
example7 (N L (N t2_1 t2_2)) = false ->
size (N L (N t2_1 t2_2)) = 3
subgoal 3 is:
example7 (N (N t1_1 t1_2) t2) = false ->
size (N (N t1_1 t1_2) t2) = 3
For the first goal, we can again ask that the functions in the goal should be computed.
simpl.
3 subgoals
============================
false = false -> 3 = 3
Since the right hand side of the implication is a fact that is trivially true, we know that
the automatic tactic of Coq can solve this goal (but using more elementary tactics from
the table, we could use intro and reflexivity to prove it). The last two goals are
solved in the same manner as the very first one, because example7 cannot possibly have
the value false for the arguments that are given in these goals.
29
auto.
intros H; discriminate H.
intros H; discriminate H.
Qed.
Lemma flatten_aux_size :
forall t1 t2, size (flatten_aux t1 t2) = size t1 + size t2 + 1.
induction t1.
============================
forall t2 : bin, size (flatten_aux L t2) = size L + size t2 + 1
subgoal 2 is:
forall t2 : bin,
size (flatten_aux (N t1_1 t1_2) t2) =
size (N t1_1 t1_2) + size t2 + 1
There are two subgoals, the first goal requires that we prove the property when the first
argument of flatten aux is L, the second one requires that we prove the property when
the argument is N t1 1 t1 2. The proof progresses easily, using the definitions of the
two functions, which are expanded when the Coq system executes the simpl tactic. We
then obtain expressions that can be solved using the ring tactic.
intros t2.
simpl.
...
============================
S (S (size t2)) = S (size t2 + 1)
ring.
t1_1 : bin
t1_2 : bin
IHt1_1 : forall t2 : bin,
size (flatten_aux t1_1 t2) = size t1_1 + size t2 + 1
30
IHt1_2 : forall t2 : bin,
size (flatten_aux t1_2 t2) = size t1_2 + size t2 + 1
t2 : bin
============================
size (flatten_aux (N t1_1 t1_2) t2) =
size (N t1_1 t1_2) + size t2 + 1
In the second goal, we see that two induction hypotheses have been provided for the
two subterms t1 1 and t1 2. We can again, request that Coq computes symbolically
the values of the two functions size and flatten aux. Then, we can use the induction
hypotheses.
intros t2; simpl.
...
============================
size (flatten_aux t1_1 (flatten_aux t1_2 t2)) =
S (size t1_1 + size t1_2 + size t2 + 1)
rewrite IHt1_1.
...
============================
size t1_1 + size (flatten_aux t1_2 t2) + 1 =
S (size t1_1 + size t1_2 + size t2 + 1)
rewrite IHt1_2.
...
============================
size t1_1 + (size t1_2 + size t2 + 1) + 1 =
S (size t1_1 + size t1_2 + size t2 + 1)
ring.
Proof completed.
Qed.
At this point, you should be able to perform your own proof by induction.
Exercise on flatten and size Prove
Hint: you should use flatten aux size, either with the tactic apply or with the
tactic rewrite.
31
Lemma not_subterm_self_l : forall x y, ~ x = N x y.
============================
forall x y : bin, x <> N x y
When reading this goal, we observe that negation of an equality is actually displayed as
<> . We will perform this proof by induction on x.
induction x.
2 subgoals
============================
forall y : bin, L <> N L y
subgoal 2 is:
forall y : bin, N x1 x2 <> N (N x1 x2) y
The first goal expresses that two constructors of the inductive type are different. This is
a job for discriminate. The first goal is solved and we then see the second goal.
intros y; discriminate.
IHx1 : forall y : bin, x1 <> N x1 y
IHx2 : forall y : bin, x2 <> N x2 y
============================
forall y : bin, N x1 x2 <> N (N x1 x2) y
Since the goal’s conclusion is a negation, we can use the corresponding tactic from the ta-
ble on page 13. This example also shows that the negation of a fact actually is represented
by a function that says “this fact implies False”.
intros y abs.
abs : N x1 x2 = N (N x1 x2) y
============================
False
The hypothesis abs expresses the equality between N x1 x2 and N (N x1 x2) y. The
constructor N is injective on both its arguments, so that this implies that x1 is equal to
N x1 x2. This reasoning step is expressed by the tactic injection:
injection abs.
============================
x2 = y -> x1 = N x1 x2 -> False
Two new equations have been added to the goal’s conclusion, but as premises of impli-
cations. These equations can be introduced as new hypotheses.
intros h2 h1.
h2 : x2 = y
h1 : x1 = N x1 x2
============================
False
32
The hypothesis h1 is actually contradictory with an instance of the hypothesis IHx1. We
can express this with the following tactics.
Print nat.
Inductive nat : Set := O : nat | S : nat -> nat
It also provides addition, multiplication, subtraction (with the special behavior that x -
y is 0 when x is smaller than y). This package also provides a tactic ring, which solves
equalities between expressions modulo associativity and commutativity of addition and
multiplication and distributivity. For natural numbers, ring does not handle subtraction
well, because of its special behavior. As we already mentioned, there is a syntactic facility,
so that 3 actually represents S (S (S O)).
Since natural numbers are given as an inductive type, we can define recursive functions
with natural numbers as input. The constraints on recursive calls makes that functions
that call themselves recursively on the predecessor of the initial argument will be easy to
define. For instance, the following definition works for the factorial function:
33
The package named ZArith provides two inductive data-types to represent integers. The
first inductive type, named positive, follows a binary representation to model the pos-
itive integers (from 1 to infinity) and the type Z is described as a type with three con-
structors, one for positive numbers, one for negative numbers, and one for 0. The package
also provides orders and basic operations: addition, subtraction, multiplication, division,
square root. The tactic ring also works for integers (this time, subtraction is well sup-
ported) The tactic omega works equally well to solve problems consisting of collections of
comparisons between linear formulas for both natural numbers of type nat and integers
of type Z.
When working with integers instead of natural numbers it is handy to instruct Coq
to interpret all numeric notations as notations concerning integers. This is done with the
following command:
The type Z is not suited for structural recursion, so that it is not easy to define recursive
functions with positive or negative integers as input. The main solution is to use well-
founded recursion as described in [1], chapter 15. An alternative approach, less powerful
but easy to understand, is to rely on an iterator that repeats the same operation a given
number of times, where the number is described as an integer. This iterator is called
iter.
Check iter.
iter : Z -> forall A : Type, (A -> A) -> A -> A
For instance, one can define the factorial function and test it as in the following dialog.
We use an auxiliary function that takes a pair of numbers as input and returns a pair of
numbers. When the input contains n and n! the output contains n + 1 and (n + 1)!. We
give this function as input to iter:
Proving properties about functions defined using iter requires knowledge that goes be-
yond this short tutorial, but we give an example in the section that also contains the
solutions to exercises at the end of this tutorial.
From now, we will revert to only manipulating natural numbers, we tell the Coq
system to forget about the specification notations for integers.
34
8 Inductive properties
Inductive definitions can also be used to describe new predicates. A predicate on a type A
is simply described as a function that takes as input elements of A and returns an element
of the type of propositions, called Prop. The constructors of the inductive propositions
are the theorems that characterize the predicate. In principle, every proof of a statement
based on this predicate is obtained as a combination of the constructors, and only the
constructors.
For example, a predicate on natural members that describes exactly the even numbers
can be described by the following inductive definition.
x : nat
H : even x
============================
exists y : nat, 0 = 2 * y
In the first goal, the system describes the case where even x is proved using the first
constructor. In this case, the goal is displayed with the variable x replaced by 0. we can
prove this goal by providing 0 for the variable y.
exists 0; ring.
...
============================
forall x0 : nat,
even x0 -> (exists y : nat, x0 = 2 * y) ->
exists y : nat, S (S x0) = 2 * y
35
Solving this goal reveals the next goal, which describes the case where even x is proved
using the second constructor. In this case, there must be a variable x0 such that x is
S (S x0), and the proposition even x0 must hold. In addition, the elim tactic generates
an induction hypothesis corresponding to the proposition even x0. Thus x0 is supposed
to satisfy the property that we want to prove for S (S x0). In this sense, the tactic elim
creates an induction hypothesis, exactly like induction (you can actually use induction
instead of elim, the two tactics are almost the same).
Now, IHx is the induction hypothesis. It says that if x is the successor’s successor of x0
then we already know that there exists a value y that is the half of x0. We can use this
value to provide the half of S (S x0). Here are the tactics that complete the proof.
In this example, we used a variant of the destruct tactic that makes it possible to choose
the name of the elements that destruct creates and introduces in the context.
The inversion tactic can be used to study the various ways in which the proof
of a statement concerning an inductive proposition could have been made. This tactic
analyses all the constructors of the inductive predicate, discards the ones that could not
have been applied, and when some constructors could have been applied it creates a new
goal where the premises of this constructor are added in the context. For instance, this
tactic is perfectly suited to prove that 1 is not even, since no constructor can conclude to
the proposition even 1 (using evenS would require that 1 = S (S x), and using even0
would require that 1 = 0):
inversion even1.
Qed.
Another example using the inversion tactic shows that when a number is even the
predecessor of its predecessor is also even. In this statement, we write the first number
as S (S x) and the predecessor of its predecessor as x.
36
Lemma even_inv : forall x, even (S (S x)) -> even x.
intros x H.
H : even (S (S x))
============================
even x
When the tactic analyzes the constructors of the even predicate, it recognizes that even0
could not prove the statement of the hypothesis and only evenS could have been used.
Therefore there must exist a variable x0 such that even x0 holds and S (S x) = S (S
x0); in other words, even x holds.
inversion H.
1 subgoal
x : nat
H : even (S (S x))
x0 : nat
H1 : even x
H0 : x0 = x
============================
even x
assumption.
Qed.
Please note that the statements of even inv and evenS are similar but inverted. This is
the reason for the tactic name.
Inductive properties can be used to express very complex notions. For instance, the
semantics of a programming language can be defined as an inductive definition, using
dozens of constructors, each one describing a an elementary step of computation.
9 Exercises
This section recapitulates the exercises from these notes. some are taken from [1].
Exercise on functions Write a function that takes five arguments and returns their
sum, use Check to verify that your description is well-formed, use Eval to force its
computation on a sample of values.
Exercise on lists, map, and app Define a function that takes as input a number n and
returns a list with n elements, from 0 to n − 1.
Exercise on sorting Define a function that takes a list as input and returns true when
it has less than 2 elements or when the first element is smaller than the second one.
Then define a function that is takes a list as input and returns true exactly when
this list is sorted (Hint: when the list has at least two elements, the first element
must be smaller than the second element and the tail must be sorted).
37
Exercise on counting Knowing that the Coq system provides a function beq nat to
compare two natural numbers, define a function that takes a natural number and
a list and returns the number of times the natural number occurs in the list.
Two benefits can be taken from this exercise. In a first step you should try using
only the basic tactics given in the table page 13. In a second step, you can verify
which of these statements are directly solved by the tactic intuition.
Exercise on universal quantification Prove
Prove the following statements (it is best to take them in order, as each can be used
for the next one).
Exercise on the sum of odd numbers The sum of the first n odd natural numbers
is defined with the following function:
Exercise on datatype definition Define a datatype where there are three cases: a
constant, a case where there are three fields, where the first field is a number and
the next two fields are in the datatype being defined, and a case with four fields,
where the first field is a boolean value and the three other fields are in the datatype
being defined.
38
Exercise on flatten and size Prove
Hint: you should use flatten aux size, either with the tactic apply or with the
tactic rewrite.
10 Solutions
The solutions to the numbered exercises are available from the Internet (on the site
associated to the reference [1]).
10.1 functions
Check fun a b c d e => a + b + c + d + e.
Eval compute in (fun a b c d e => a + b + c + d + e) 1 2 3 4 5.
10.3 sorting
Definition head_sorted (l : list nat) : bool :=
match l with
a::b::_ => leb a b
| _ => true
end.
10.4 counting
Fixpoint count (n : nat) (l : list nat) : nat :=
match l with
nil => 0
| p::tl => if beq_nat n p then 1 + count n tl
end.
39
10.5 logical connectives
Lemma lc1 : forall A B C:Prop, A/\(B/\C) -> (A/\B)/\C.
intros A B C H; destruct H as [H H1]; destruct H1 as [H1 H2].
split. split. assumption. assumption. assumption.
Qed.
40
induction n; intros m; simpl.
reflexivity.
rewrite IHn; reflexivity.
Qed.
10.10 flatten
Here is the solution to the exercise on flatten and size (this re-uses the lemma
flatten aux size proved in these notes).
Lemma flatten_size : forall t, size(flatten t) = size t.
Proof.
intros t; elim t.
simpl. reflexivity.
intros t1 IH1 t2 IH2; simpl. rewrite flatten_aux_size. rewrite IH2. ring.
Qed.
41
10.11 Proof about Z fact
We show that the factorial function computed on integers coincides with the factorial
function computed on natural numbers. This is a simple proof by induction over natural
numbers, but a lot of work is spent using the correspondances between integers and
natural numbers. We need to first establish a lemma about the function iter to establish
a correspondance with the corresponding iterator on natural numbers. This script uses
tactics that have not been presented in this tutorial (unfold, replace,trivial) but
whose documentation can be found in [2].
Lemma iter_nat_of_Z : forall n A f x, 0 <= n ->
iter n A f x = iter_nat (Zabs_nat n) A f x.
intros n A f x; case n; auto.
intros p _; unfold iter, Zabs_nat; apply iter_nat_of_P.
intros p abs; case abs; trivial.
Qed.
Lemma fact_aux_correct :
forall n, fact_aux (Z_of_nat n) = (Z_of_nat n, Z_of_nat (nat_fact n)).
intros n; unfold fact_aux; rewrite iter_nat_of_Z; [ | apply Zle_0_nat].
induction n; [auto | ].
replace (nat_fact (S n)) with (S n * nat_fact n)%nat by auto.
assert(U : forall k A f x, iter_nat (S k) A f x=f (iter_nat k A f x)) by auto.
rewrite Zabs_nat_Z_of_nat in IHn |- *; rewrite U, IHn; unfold fst, snd.
rewrite inj_mult, Zmult_comm, inj_S; unfold Zsucc; auto.
Qed.
Lemma Z_fact_correct :
forall n:nat, Z_fact (Z_of_nat n) = Z_of_nat (nat_fact n).
intros n; unfold Z_fact; rewrite fact_aux_correct; auto.
Qed.
References
[1] Yves Bertot and Pierre Castéran. Interactive Theorem Proving and Program Devel-
opment, Coq’Art:the Calculus of Inductive Constructions. Springer-Verlag, 2004.
[2] The Coq development team. The Coq proof Assistant Reference Manual, Ecole
Polytechnique, INRIA, Universit de Paris-Sud, 2004. http://coq.inria.fr/doc/
main.html
42