Pour Coq, un entier naturel est soit
On dit que la constante O et la fonction S sont les constructeurs du type nat. En Coq :
0
(le chiffre zéro)
pour zéro au lieu de
O
(la lettre O).
discriminate H
demande à Coq de vérifier que la preuve
H
d'une égalité entre deux termes écrits uniquement avec des constructeurs
est contradictoire, c'est-à-dire est une preuve de
False
.
N'oubliez pas que de
False
, on prouve n'importe quoi.
Définissons maintenant l'addition des entiers relatifs. Dans un premier temps, on note add n m la somme de n et m.
Explications : add n m regarde si n vaut 0, si oui il renvoie m, sinon c'est que n = S p pour un certain p. On calcule alors (add p m) et on prend le successeur de ce nombre là. Cette définition de l'addition est récursive (en Coq, "Fixpoint" pour une définition récursive) : la fonction add est elle-même utilisée dans sa définition. Pourtant il n'y a pas de raisonnement circulaire, cette définition permet bien de calculer (sinon, Coq ne nous aurait pas laissé faire de toute façon). Cette définition est un programme : on a non seulement déclaré une fonction qui prend deux entiers et en retourne un, mais en plus on a dit comment calculer le résultat. Ceci permet à Coq de faire les calculs lui-même :
add
:
add (S (S 0)) (S (S (S 0))).
En fait Coq dispose des notations usuelles pour l'addition (et la plupart des opérations usuelles.
Nous allons maintenant devoir prouver des égalités.
simpl
demande à Coq de
calculer
dans le but en utilisant
« bêtement »
la définition
(et seulement la définition) d'une (ou
plusieurs) fonctions, autant que possible. Elle n'est pas bien nommée,
et devrait plutôt s'appeler «
compute
» ou quelque chose comme ça.
reflexivity
demande à Coq de conclure la preuve en
constatant que les membres de gauche et de droite d'une égalité
sont les mêmes.
rewrite
La tactique
rewrite
est une tactique très importante, probablement la plus
utilisée de toutes !
Elle permet de réécrire à l'aide d'une égalité.
Si vous avez une hypothèse
H : a = b
alors
rewrite H
va transformer chaque
a
du but par un
b
.
rewrite
Comme avec
exact
(ou en fait avec toutes les autres tactiques), on peut
utiliser des théorèmes déjà prouvés, dont la conclusion est une égalité,
pour faire des réécritures avec
rewrite
.
On peut préciser à quels termes on applique ces théorèmes.
Dans l'exemple important ci-dessous, tous les arguments sont précisés, on a
aussi inséré des commandes
Check
pour que vous voyiez les énoncés précis
utilisés, avant la réécriture (bien sûr, dans une vraie preuve on n'écrit
pas ces
Check
).
simpl
, prouver le théorème suivant.
add_0_l
et
add_1_l
, ainsi que le
« théorème »
deux_et_deux_font_quatre
.
En fait, il arrive souvent qu'il n'y ait pas tellement le choix pour les
arguments des théorèmes utilisés dans
rewrite
, ou bien, s'il y a le choix,
que Coq décide pour vous de façon assez raisonnable.
Alors, on peut omettre les arguments des théorèmes.
Si on reprend l'exemple précédent en laissant Coq unifier/décider tout seul,
ça donne :
En fait, avec
add_0_l
et
add_succ_l
on a deux résultats qui suffisent,
avec
rewrite
, à prouver tous les autres. On peut aussi utiliser
simpl
,
mais en fait
simpl
a un certain nombre de défauts :
rewrite
plus précis et
prévisibles.
simpl
.
intros
,
rewrite
et
reflexivity
et
les théorèmes
add_0_l
,
add_succ_l
et
add_1_l
.
Dans un premier temps, vous donnerez dans les
rewrite
les arguments,
dans un second temps, voir si vous pouvez les omettre et laisser Coq
remplir tout seul les trous.
destruct
(encore)
La façon dont sont construits les naturels en Coq fait apparaître deux
cas
bien différents : un entier naturel, disons
n
vaut :
0
(S n')
où
n'
est un autre entier.
destruct
sur une variable de type entier (en général
de n'importe quel type inductif) permet les preuves par cas.
Dans la tactique
destruct n as [| n'
] on retrouve la syntaxe
quelque_chose | autre_chose
, qu'on avait vue lorsqu'on détruit les
\/
.
Ici :
quelque_chose
est vide car c'est le cas n = 0, donc avec le constructeur
sans argument
autre_chose
est
n'
: on nomme l'entier n' tel que n = (S n').
Bien sûr on aurait pu choisir n'importe quel autre nom que n'.
Remarque : on vient de voir qu'on peut aussi utiliser
rewrite
dans une
hypothèse à l'aide de la syntaxe
rewrite règle in nom_hypothese
.
Indice : il faut faire une deuxième preuve par cas dans un des cas.
Les tactiques
simpl
,
destruct
,
reflexivity
et
rewrite
ne suffisent pas toujours :
Nous avons besoin de prouver ce théorème par induction. Une preuve par induction, aussi appelée preuve par récurrence sur entier naturel n a deux parties
n
vaut
0
,
n
s'écrit
S n'
pour un certain
n'
, on prouve
la propriété pour
n
sous l'hypothèse qu'elle est vraie pour
n'
(on appelle cette hypothèse « hypothèse d'induction »).
Dans la tactique
induction n as [|n' IH
], il y a :
|
car c'est le cas
0
, sans argument
|
il y a
n'
car c'est le cas
n = (S n')
et
il faut bien donner un nom à cette nouvelle variable et aussi un nom
(ici
IH
) pour l'hypothèse que
n'
vérifie la propriété qu'on cherche à
prouver.
Avec les tactiques
intros
,
apply
,
destruct
,
simpl
,
discriminate
,
exfalso
,
reflexivity
,
induction
et
rewrite
vous pouvez écrire
énormément de preuves
Remarque : le choix de la variable sur laquelle porte l'induction
est important, il est guidé par la façon dont la fonction
add
a
été écrite. Comme cette fonction sépare les cas suivant
son premier argument
, les preuves sur
+
se feront par induction sur
l'opérande de gauche.
Pour finir cette partie sur l'addition, quelques remarques de qualité de vie
sur
rewrite
.
Si vous avez suivi notre conseil sur
simpl
(ne pas trop l'utiliser), vous
avez dû écrire beaucoup de
rewrite
.
En fait, on peut enchaîner les réécritures avec la syntaxe
rewrite regle_1, regle_2, ..., regle_n
.
Voici un exemple (avec de plus, un théorème qui sert souvent).
Remarquer également qu'on a donné explicitement certains arguments.
La multiplication est aussi définie récursivement :
Avec des mots :
n = 0
, alors
mul n m = 0
.
n = (S p)
pour un certain
p
, et alors
mul (S p) m = m + (mul p m)
.
(p + 1) * m = p * m + 1 * m = p * m + m = m + p * m
.
Dans la bibliothèque de Coq, la notation
(n * m)
correspond à
(mul n m)
.
(mul 2 3)
. Les évaluations de l'addition
peuvent être faites directement.
Comme pour l'addition précédente, la définition de la multiplication
est
constructive
, c'est-à-dire permet effectivement de calculer.
On peut donc aussi utiliser la tactique
simpl
pour demander à Coq de
calculer avec
mul
.
Voir les exemples suivants :
Comme la multiplication fait intervenir l'addition dans sa définition, vous aurez à utiliser les théorèmes déjà prouvés sur l'addition. Pour rappel, ceux-ci sont :
add_0_l : ∀ n : nat, 0 + n = n
add_0_r : ∀ n : nat, n + 0 = n
add_1_l : ∀ n : nat, 1 + n = S n
add_1_r : ∀ n : nat, n + 1 = S n
add_succ_l : ∀ n m : nat, S n + m = S (n + m)
add_succ_r : ∀ n m : nat, n + S m = S (n + m)
add_comm : ∀ n m : nat, n + m = m + n
add_assoc : ∀ n m p : nat, n + (m + p) = (n + m) + p
Comme pour l'addition, nous avons maintenant avec
mul_succ_l
et
mul_0_l
tout ce qu'il faut pour calculer avec
rewrite
, il vaut mieux éviter
simpl
.
En général, les théorèmes et hypothèses sont écrits de façon à ce que le
sens « naturel » de réécriture soit de gauche à droite (dans un certain
sens, le membre de droite de l'égalité est « plus simple » que le membre de
gauche). Il arrive toutefois qu'on doive réécrire en utilisant l'égalité
de droite à gauche. Ceci se fait avec la syntaxe
rewrite <-regle
, comme
le montre l'exemple suivant.
Avant de passer à quelque chose de plus sérieux (et difficile) un petit
exercice stupide pour s'exercer.
Utiliser
add_assoc
et l'hypothèse de droite à gauche.
N'utiliser
simpl
qu'à la toute fin, lorsqu'il n'y a que des nombres
littéraux.
Celui ci est un peu embêtant... mais est un passage obligé pour prouver que la multiplication est commutative. Si vraiment, vous n'y arrivez pas après avoir beaucoup essayé, passez à la suite et revenez plus tard.
Nous avons d'abord vu que les entiers naturels sont définis en Coq par
induction. Il y a
0
, puis
S 0
, puis
S (S 0)
, ...
Par définition, deux entiers naturels écrits uniquement avec des
constructeurs sont différents s'ils sont syntaxiquement différents.
La tactique
discriminate nom_d'hypothese
permet de trouver une
contradiction (donc de terminer la preuve) de ce type dans une égalité.
Un aspect important de Coq est qu'il est capable de
calculer
. Nous
avons vu qu'avec la tactique
simpl
, il calcule autant qu'il peut en
utilisant les définitions données.
La tactique
reflexivity
permet de terminer une preuve d'une égalité
en demandant à Coq de constater que les membres gauche et droit d'une
égalité sont les mêmes.
La tactique
rewrite preuve_egalite
utilise la preuve d'une égalité
disons
a = b
pour changer tous les
a
du but en
b
. Nous avons vu
qu'avec un théorème du type égalité universellement quantifiée, on peut
laisser Coq unifier (remplir les trous) ou bien au contraire les remplir
nous-mêmes. La variante
rewrite preuve_egalite in nom_d'hypothèse
permet
de réécrire dans une hypothèse plutôt que dans le but. Il est possible
d'enchaîner les réécritures en séparant les règles par des virgules.
Enfin, il est possible de réécrire avec une égalité vue de droite à gauche
avec la syntaxe
rewrite <-preuve_egalite
.
Ensuite, nous avons vu l'induction sur les entiers (la récurrence),
avec la tactique
induction nom_var as [| autre_nom_var nom_hypothese
.]
Enfin, nous avons prouvé de nombreuses propriétés très utiles de l'addition
et de la multiplication (mais inutile de les apprendre par coeur) :
add_0_l : ∀ n : nat, 0 + n = n
add_0_r : ∀ n : nat, n + 0 = n
add_1_l : ∀ n : nat, 1 + n = S n
add_1_r : ∀ n : nat, n + 1 = S n
add_succ_l : ∀ n m : nat, S n + m = S (n + m)
add_succ_r : ∀ n m : nat, n + S m = S (n + m)
add_comm : ∀ n m : nat, n + m = m + n
add_assoc : ∀ n m p : nat, n + (m + p) = (n + m) + p
mul_0_l : ∀ n : nat, 0 * n = 0
mul_0_r : ∀ n : nat, n * 0 = 0
mul_1_l : ∀ n : nat, 1 * n = n
mul_1_r : ∀ n : nat, n * 1 = n
mul_succ_l : ∀ n m : nat, S n * m = n * m + m
mul_succ_r : ∀ n m : nat, n * S m = n * m + n
mul_comm : ∀ n m : nat, n * m = m * n
mul_add_distr_l : ∀ n m p : nat, n * (m + p) = n * m + n * p
mul_add_distr_r : ∀ n m p : nat, (n + m) * p = n * p + m * p
mul_assoc : ∀ n m p : nat, n * (m * p) = n * m * p
mul_eq_0: ∀ n m : nat, n * m = 0 ↔ n = 0 ∨ m = 0