Entiers naturels (1ère partie)

Définition inductive des entiers naturels

Pour Coq, un entier naturel est soit

  • O (la lettre O qui représente l'entier 0),
  • soit l'image par la fonction S (pour "successeur") d'un entier naturel.
Ainsi :
  • O représente zéro
  • S O est le successeur de zéro donc un
  • S (S O) est le successeur de un donc deux
  • ...
Heureusement Coq permet la notation usuelle (décimale) de ces entiers, mais ce n'est qu'une notation pratique !

On dit que la constante O et la fonction S sont les constructeurs du type nat. En Coq :

  • les résultats de constructeurs différents sont toujours différents,
  • deux termes composés uniquement de constructeurs ne peuvent être égaux que s'ils s'écrivent de la même manière.
À partir de maintenant, on utilisera la notation 0 (le chiffre zéro) pour zéro au lieu de O (la lettre O). La tactique 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.

Exercice : Prouver le théorème suivant.

Exercice : Prouver le théorème suivant.

N'oubliez pas que de False, on prouve n'importe quoi.

Addition des entiers naturels

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 :

Exercice qui a l'air bête mais est très important :

Calculer à la main (papier et crayon), en utilisant la définition de 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.

  • La tactique 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.
  • La tactique reflexivity demande à Coq de conclure la preuve en constatant que les membres de gauche et de droite d'une égalité sont les mêmes.

Exercice : Prouver le théorème suivant.

Exercice : Prouver le théorème suivant.

Exercice : Prouver le théorème suivant.

Exercice : Prouver le théorème suivant.

La tactique 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.

Premiers exemples de rewrite

Exercice : Prouver le théorème suivant.

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).

Exercice : sans utiliser simpl, prouver le théorème suivant.

C'est juste pour s'entraîner à rewrite et bien voir comment ça transforme le but). Vous utiliserez les règles add_0_l et add_1_l, ainsi que le « théorème » deux_et_deux_font_quatre.

Unification, encore

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 :

  • souvent il en fait trop
  • pédagogiquement, cela ne correspond pas vraiment à quelque chose qu'on fait en mathématiques
Donc à part dans le cas où de nouvelles opérations sont définies, il vaut mieux éviter de l'utiliser et faire des rewrite plus précis et prévisibles.

Exercice : Prouver le théorème suivant sans simpl.

Utiliser uniquement les tactiques 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.

Preuve par cas : 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 :

  • soit 0
  • soit (S n')n' est un autre entier.
En Coq, la tactique 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.

Exercice : Prouver le théorème suivant avec une preuve par cas .

Exercice : Prouver le théorème suivant avec une preuve par cas .

Indice : il faut faire une deuxième preuve par cas dans un des cas.

Bilan

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. 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_1_l : ∀ n : nat, 1 + n = S n
  • add_succ_l : ∀ n m : nat, S n + m = S (n + m)
La liste est très courte pour l'instant ! Pour prouver plus de lemmes, nous aurons besoin d'une nouvelle méthode de preuve : la preuve par induction (aussi appelée par récurrence sur les entiers naturels). Ceci sera fait dans le sujet de TP suivant.