Library Basics
Definition admit {T: Type} : T. Admitted.
Introduction
Types énumérés
Inductive jour : Type :=
| lundi : jour
| mardi : jour
| mercredi : jour
| jeudi : jour
| vendredi : jour
| samedi : jour
| dimanche : jour.
Le type s'appelle jour et ses éléments sont lundi,
mardi, etc... Les lignes 2 à 8 de la définition peuvent se lire
«lundi est un jour, mardi est un jour, etc.»
Ayant défini jour, on peut écrire des fonctions qui agissent dessus.
Definition jour_ouvre_suivant (d:jour) : jour :=
match d with
| lundi => mardi
| mardi => mercredi
| mercredi => jeudi
| jeudi => vendredi
| vendredi => lundi
| samedi => lundi
| dimanche => lundi
end.
Une chose à noter est que les types de l'argument et du
résultat de cette fonction sont déclarés explicitement. Comme la
plupart des langages de programmation fonctionnels, Coq sait
souvent deviner ces types, même s'ils ne sont pas donnés
explicitement -- c'est-à-dire qu'il fait de l' inférence de types --
mais nous les incluerons toujours pour faciliter la lecture.
Ayant défini une fonction, nous devrions commencer par
vérifier qu'elle a le comportement attendu sur quelques
exemples. Il y a en fait trois moyens différents de le faire en
Coq. Premièrement, nous pouvons utiliser la commande Eval simpl
pour évaluer une expression composée utilisant
jour_ouvre_suivant.
Eval simpl in (jour_ouvre_suivant vendredi).
Eval simpl in (jour_ouvre_suivant (jour_ouvre_suivant samedi)).
Si vous avez un ordinateur sous la main, c'est le moment de
lancer Coq dans votre IDE favori -- soit CoqIde soit Proof General --
et d'essayer vous-même. Ouvrez ce fichier (Basics.v), que vous
trouverez dans les sources accompagnant le livre, trouvez l'exemple
ci-dessus, soumettez-le à Coq et observez le résultat.
Le mot-clef simpl ("simplifier") indique à Coq quelle
méthode utiliser pour évaluer l'expression qu'on lui fournit.
Pour le moment, simpl est la seule dont on a besoin; plus bas,
nous verrons des alternatives qui sont parfois utiles.
Deuxièmement, nous pouvons noter ce qu'on s'attend à obtenir
comme résultat, sous forme d'un exemple Coq:
Example teste_jour_ouvre_suivant:
(jour_ouvre_suivant (jour_ouvre_suivant samedi)) = mardi.
Cette déclaration fait deux choses: elle fait une assertion
(que le deuxième jour ouvré suivant samedi est mardi) et elle
donne un nom à cette assertion, qu'on peut utiliser plus tard pour
y faire référence. Ayant fait notre assertion, nous pouvons aussi demander à
Coq de la vérifier, comme ceci:
Proof. simpl. reflexivity. Qed.
Les détails de cette preuve ne sont pas importants pour
l'instant (nous y reviendrons sous peu), mais en gros elle peut se
lire: «l'assertion qu'on vient de faire se prouve en observant que les
deux membres de l'égalité sont les mêmes après simplification.»
Troisièmement, nous pouvons demander à Coq d'«extraire», à partir
d'une Definition, un programme écrit dans un autre langage de
programmation plus conventionnel (OCaml, Scheme, or Haskell), doté
d'un compilateur très efficace. Cette possibilité est très
intéressante, parce qu'elle permet la construction de programmes
entièrement certifiés dans des langages très répandus. C'est
d'ailleurs l'un des usages principaux ayant motivé le
développement de Coq. Nous reviendrons sur ce sujet dans des
chapitres ultérieurs. On trouve aussi de plus amples informations
dans le livre Coq'Art de Bertot et Castéran, ainsi que dans le
manuel de référence.
Booléens
Inductive bool : Type :=
| true : bool
| false : bool.
Nous définissons ici nos propres booléens, pour montrer
comment faire à partir de rien. Mais bien sûr, la bibliothèque
standard de Coq fournit une implantation par défaut des booléens,
ainsi qu'une multitude de fonctions et de lemmes utiles. (Le lecteur
intéressé pourra jeter un oeil à Coq.Init.Datatypes dans la
documentation de la bibliothèque standard.) Autant que possible, nous
nommerons nos définitions et théorèmes pour qu'ils coïncident
exactement avec ceux de la bibliothèque standard.
On peut définir, comme ci-dessus sur les jours de la
semaine, des fonctions sur les booléens:
Definition negb (b:bool) : bool :=
match b with
| true => false
| false => true
end.
Definition andb (b1:bool) (b2:bool) : bool :=
match b1 with
| true => b2
| false => false
end.
Definition orb (b1:bool) (b2:bool) : bool :=
match b1 with
| true => true
| false => b2
end.
Les deux dernères illustrent la syntaxe pour les définitions
de fonctions à plusieurs arguments.
Les «tests unitaires» suivants constituent une
spécification complète -- une table de vérité -- de la fonction orb:
Example test_orb1: (orb true false) = true.
Proof. reflexivity. Qed.
Example test_orb2: (orb false false) = false.
Proof. reflexivity. Qed.
Example test_orb3: (orb false true) = true.
Proof. reflexivity. Qed.
Example test_orb4: (orb true true) = true.
Proof. reflexivity. Qed.
(Observons qu'on a omis le simpl dans les preuves. Il n'est pas
nécessaire, car reflexivity effectue la même simplification
automatiquement.
Note sur les notations : on utilise les crochets pour
délimiter les fragments de code Coq dans les commentaires des fichiers
.v; cette convention, utilisée aussi par l'outil coqdoc de
documentation, les sépare visuellement du texte autour. Dans la
version html de ces fichiers, ces parties du texte apparaissent dans
une police différente.
Les valeurs Admitted et admit peuvent servir à combler
un trou dans une définition ou une preuve incomplète. Nous les
utiliserons dans les exercices suivants. En général, votre
travail dans les exercices consiste à remplacer admit ou
Admitted par des définitions et preuves complètes.
Cette fonction doit renvoyer true si au moins l'un de ses
arguments est false.
Exercice : 1 étoile (nandb)
Compléter la définition de la fonction suivante, puis s'assurer que les assertions Example ci-dessous sont toutes validées par Coq.Definition nandb (b1:bool) (b2:bool) : bool :=
admit.
Remplacer "Admitted." dans chaque preuve par
"Proof. reflexivity. Qed."
Example test_nandb1: (nandb true false) = true.
Admitted.
Example test_nandb2: (nandb false false) = true.
Admitted.
Example test_nandb3: (nandb false true) = true.
Admitted.
Example test_nandb4: (nandb true true) = false.
Admitted.
☐
Exercice : 1 étoile (andb3)
Faire la même chose pour la fonction andb3 ci-dessous. Cette fonction doit renvoyer true quand tous ses arguments sont true et false sinon.Definition andb3 (b1:bool) (b2:bool) (b3:bool) : bool :=
admit.
Example test_andb31: (andb3 true true true) = true.
Admitted.
Example test_andb32: (andb3 false true true) = false.
Admitted.
Example test_andb33: (andb3 true false true) = false.
Admitted.
Example test_andb34: (andb3 true true false) = false.
Admitted.
☐
Types fonctionnels
Check true.
Check (negb true).
Les fonctions, telles que negb, sont elle-mêmes des valeurs, des données, exactement comme
true et false. Leurs types sont appelés types fonctionnels et sont notés avec une flèche.
they are written with arrows.
Check negb.
Le type de negb, noté bool -> bool et prononcé «bool flèche
bool», se comprend comme: «étant donné un argument de type bool,
cette fonction renvoie un résultat de type bool.» De la même
manière, le type de andb, noté bool -> bool -> bool, se comprend comme:
«étant donnés deux arguments, tous deux de type bool, cette
fonction renvoie un résultat de type bool».
Nombres
Module Playground1.
Les types définis jusqu'ici sont des exemples de «types
énumérés»: leurs définitions énumèrent explicitement un ensemble fini
d'éléments. Une manière plus élaborée de définir un type est de donner
une collection de «règles inductives» décrivant ses éléments. Par
exemple, on peut définir les entiers naturels comme suit:
Inductive nat : Type :=
| O : nat
| S : nat -> nat.
Les clauses de cette définition peuvent se comprendre comme:
Entrons un peu plus dans le détail.
Tout ensemble défini inductivement (jour, nat, bool, etc.) est en
fait un ensemble d' expressions. La définition de nat dit comment
les expressions de l'ensemble nat sont construites:
Les mêmes règles s'appliquent pour nos définitions de jour et
bool. Les annotations utilisées pour leurs constructeurs sont
analogues à celle du constructeur O; elles indiquent que ces
constructeurs n'attendent pas d'arguments.
Ces trois conditions inductives font précisément la force
de la déclaration Inductive. Elles impliquent que l'expression O,
l'expression S O, l'expression S (S O), l'expression S (S (S
O)), et ainsi de suite, appartiennent toutes à l'ensemble nat,
contrairement à d'autres expressions comme true, andb true false, ou
S (S false).
On peut écrire des fonctions simples par cas sur les entiers
exactement comme on l'a fait plus haut -- par exemple, la fonction
prédécesseur:
- O est un entier naturel (observons qu'on utilise la lettre «O», pas le nombre «0»).
- S est un «constructeur» qui prend en argument un entier naturel et en rend un autre -- c'est-à-dire que si n est un entier, alors S n en est un aussi.
- l'expression O appartient à l'ensemble nat;
- si n est une expression appartenant à l'ensemble nat, alors S n aussi; et
- les expressions formées de ces deux manières sont les seules appartenant à l'ensemble nat.
Definition pred (n : nat) : nat :=
match n with
| O => O
| S n' => n'
end.
La seconde branche peut se comprendre comme «si n est de
la forme S n' pour un certain n', alors renvoyer n'».
End Playground1.
Definition minustwo (n : nat) : nat :=
match n with
| O => O
| S O => O
| S (S n') => n'
end.
Les entiers naturels sont utilisés partout, ce qui justifie
un soupçon de magie built-in pour les écrire et les afficher: les
nombres arabes ordinaires peuvent être utilisés alternativement à
la notation unaire définie par les constructeurs S et O. Coq
affiche par défaut les entiers en notation arabe:
Check (S (S (S (S O)))).
Eval simpl in (minustwo 4).
Le constructeur S a le type nat -> nat, exactement comme les
fonctions minutstwo et pred:
Check S.
Check pred.
Check minustwo.
On appliquer ces trois valeurs à un entier pour en obtenir un
nouveau. Néanmoins, il y a une différence fondamentale: les fonctions,
comme pred et minustwo, sont équipées de règles de calcul --
e.g., la définition de pred dit que pred 2 se simplifie en 1 --
alors que la définition de S n'est pas attachée à de telles
règles. Bien qu'elle soit une fonction, en ce sens qu'on peut lui
appliquer un argument, elle ne fait rien du tout.
Pour la plupart des définitions de fonctions sur les
entiers, le raisonnement par cas ne suffit pas: on a en plus besoin de
la récursion. Par exemple, pour vérifier qu'un entier n est pair,
on peut avoir besoin de vérifier récursivement que n-2 l'est. Pour
écrire de telles fonctions, on utilise le mot-clef Fixpoint.
Fixpoint evenb (n:nat) : bool :=
match n with
| O => true
| S O => false
| S (S n') => evenb n'
end.
On peut définir oddb à l'aide d'une déclaration Fixpoint
similaire, mais on préfère une définition plus simple d'utilisation:
Definition oddb (n:nat) : bool := negb (evenb n).
Example test_oddb1: (oddb (S O)) = true.
Proof. reflexivity. Qed.
Example test_oddb2: (oddb (S (S (S (S O))))) = false.
Proof. reflexivity. Qed.
Bien sûr, on peut aussi définir récursivement des fonctions
à plusieurs arguments. (On utilise à nouveau un module pour éviter de
polluer l'espace de noms.)
Module Playground2.
Fixpoint plus (n : nat) (m : nat) : nat :=
match n with
| O => m
| S n' => S (plus n' m)
end.
Additionner trois et deux donne maintenant bien cinq.
Eval simpl in (plus (S (S (S O))) (S (S O))).
La simplification effectuée par Coq pour arriver à ce
résultat peut être vue comme ceci:
plus (S (S (S O))) (S (S O))
==> S (plus (S (S O)) (S (S O))) par la seconde clause du match
==> S (S (plus (S O) (S (S O)))) par la seconde clause du match
==> S (S (S (plus O (S (S O))))) par la seconde clause du match
==> S (S (S (S (S O)))) par la première clause du match
Pour gagner en lisibilité, si deux arguments ou plus ont le
même type, on peut les grouper. Dans la définition suivante, (n m
: nat) pourrait s'écrire (n : nat) (m :
nat).
Fixpoint mult (n m : nat) : nat :=
match n with
| O => O
| S n' => plus m (mult n' m)
end.
Example test_mult1: (mult 3 3) = 9.
Proof. reflexivity. Qed.
On peut aussi raisonner par cas sur deux expressions d'un
coup, en utilisant une virgule:
Fixpoint minus (n m:nat) : nat :=
match n, m with
| O , _ => O
| S _ , O => n
| S n', S m' => minus n' m'
end.
Le _ sur la première ligne est un cas joker. Écrire _ dans un
cas revient à écrire une variable non utilisée dans le membre
droit. Cela évite d'avoir à un inventer un nom de variable
inutilement.
End Playground2.
Fixpoint exp (base power : nat) : nat :=
match power with
| O => S O
| S p => mult base (exp base p)
end.
Exercice : 1 étoile (factorielle)
Rappelons la fonction factorielle habituelle:factorial(0) = 1 factorial(n) = n * factorial(n-1) (if n>0)Traduisons-la en Coq.
Fixpoint factorial (n:nat) : nat :=
admit.
Example test_factorial1: (factorial 3) = 6.
Admitted.
Example test_factorial2: (factorial 5) = (mult 10 12).
Admitted.
☐
On peut rendre les expressions numériques légèrement plus lisibles
et plus faciles à écrire en introduisant des «notations» pour
l'addition, la multiplication et la soustraction.
Notation "x + y" := (plus x y)
(at level 50, left associativity)
: nat_scope.
Notation "x - y" := (minus x y)
(at level 50, left associativity)
: nat_scope.
Notation "x * y" := (mult x y)
(at level 40, left associativity)
: nat_scope.
Check ((0 + 1) + 1).
(Les annotations level, associativity et nat_scope
contrôlent le traitement de ces notations par le parser de Coq. Les
détails importent peu, mais le lecteur intéressé peut se référer à la
partie «Supplément sur les notations» dans la partie «Contenu
optionnel» à la fin de ce chapitre.)
Observons que ces notations ne changent pas les définitions
déjà entées: ce sont simplement des instructions au parser de Coq pour
accepter x + y à la place de just plus x y et, de même, à
l'afficheur de Coq pour afficher x + y plutôt que plus x y.
Quand on dit que Coq n'a rien de built-in, c'est pour de
vrai: même le test d'égalité entre entiers est une opération définie
au niveau utilisateur! La fonction beq_nat teste l'égalité (eq) entre entiers
naturels et renvoie un booléen. Notons l'utilisation de définitions par
cas (match) imbriquées (on aurait aussi pu utiliser un match
simultané, comme pour minus.)
Fixpoint beq_nat (n m : nat) : bool :=
match n with
| O => match m with
| O => true
| S m' => false
end
| S n' => match m with
| O => false
| S m' => beq_nat n' m'
end
end.
De la même manière, la fonction ble_nat compare les entiers
naturels (on écrit ble pour boolean less or equal).
Fixpoint ble_nat (n m : nat) : bool :=
match n with
| O => true
| S n' =>
match m with
| O => false
| S m' => ble_nat n' m'
end
end.
Example test_ble_nat1: (ble_nat 2 2) = true.
Proof. reflexivity. Qed.
Example test_ble_nat2: (ble_nat 2 4) = true.
Proof. reflexivity. Qed.
Example test_ble_nat3: (ble_nat 4 2) = false.
Proof. reflexivity. Qed.
Exercice : 2 étoiles (blt_nat)
La fonction blt_nat compare les entiers naturels au sens strict et renvoie un booléen. Plutôt que de refaire une définition par Fixpoint, la définir en utilisant une fonction définie précédemment.Definition blt_nat (n m : nat) : bool :=
admit.
Example test_blt_nat1: (blt_nat 2 2) = false.
Admitted.
Example test_blt_nat2: (blt_nat 2 4) = true.
Admitted.
Example test_blt_nat3: (blt_nat 4 2) = false.
Admitted.
☐
Preuve par simplification
Theorem plus_O_n : forall n : nat, 0 + n = n.
Proof.
reflexivity. Qed.
(Remarque: l'énoncé précédent apparaît peut-être différent
dans le fichier source original et dans l'affichage par Coq. Dans les
fichiers Coq, on écrit le quantificateur universel forall en utilisant le
mot-clef « forall ». Ce dernier est affiché comme un «A» avec la tête
en bas, le symbole habituel en logique. )
La forme de ce théorème et de cette démonstration sont
presque les même que dans les exemples précédents: les seules
différences sont qu'on a ajouté le quantificateur forall n : nat et qu'on a
utilisé le mot-clef Theorem plutôt qu'Example. En fait, cette
seconde différence relève uniquement du style; les mots-clef Example
et Theorem (ainsi que quelques autres comme Lemma, Fact et
Remark) signifie exactement la même chose en Coq.
Les mots-clef simpl et reflexivity sont des exemples de
tactiques. Une tactique est une commande utilisée entre Proof et
Qed pour expliquer à Coq comment vérifier la correction d'un énoncé.
Nous verrons plusieurs autres tactiques dans la suite de cette séance
et d'autres encore dans les séances suivantes.
(Au passage, il sera utile plus tard de savoir que
reflexivity fait en réalité un peu plus que simpl -- par exemple,
elle essaie de «déplier» les termes définis en remplaçant les termes
par leurs définitions. Cette différence est due au fait que, si
reflexivity réussit, le but entier est atteint et on n'a pas besoin
de lire les expressions, quelles qu'elles soient, auxquelles
reflexivity est arrivée; au contraire, simpl s'utilise dans des
cas où on peut avoir à lire et comprendre le nouveau but, donc on n'a
pas envie qu'elle déplie aveuglément les définitions.)
Exercice : 1 étoile (simpl_plus)
Qu'affichera Coq en réponse à cette demande ?Eval simpl in (forall n:nat, n + 0 = n).
Et à celle-ci ?
Eval simpl in (forall n:nat, 0 + n = n).
Expliquer la différence. ☐
La tactique intros
Theorem plus_O_n'' : forall n:nat, 0 + n = n.
Proof.
intros n. reflexivity. Qed.
Jouer cette preuve étape par étape en Coq et observer
comment le but et le contexte changent.
Theorem plus_1_l : forall n:nat, 1 + n = S n.
Proof.
intros n. reflexivity. Qed.
Theorem mult_0_l : forall n:nat, 0 * n = 0.
Proof.
intros n. reflexivity. Qed.
Le suffixe _l dans les noms de ces théorèmes se prononce
«à gauche».
Theorem plus_id_example : forall n m:nat,
n = m -> n + n = m + m.
Plutôt que d'énoncer quelque chose d'entièrement universel en les
entiers n et m, ce théorème porte sur une propriété plus
spécialisée, vraie seulement si n = m. La flèche se prononce
«implique».
Comme n et m sont des entiers arbitraires, la simplification ne
suffit pas à démontrer ce théorème. Il se démontre en effet en
observant que, en supposant n=m, on peut remplacer n par m dans
le but, pour obtenir une égalité avec la même expression des deux
côtés. La tactique pour demander à Coq de faire ce remplacement
s'appelle rewrite.
Proof.
intros n m. intros H. rewrite -> H. reflexivity. Qed.
La première ligne de la preuve déplace les variables n et
m, quantifiées universellement, dans le contexte. La deuxième
déplace l'hypothèse n=m dans le contexte et la nomme H. La
troisième demande à Coq de récrire le but courant (n+n=m+m) en
remplaçant le membre gauche de l'hypothèse d'égalité H par son
membre droit.
(La flèche dans le rewrite n'a rien à voir avec l'implication: il
demande à Coq de récrire de gauche à droite. Pour récrire de droite à
gauche, on utilise rewrite <-. Essayer de faire ce changement dans la
preuve ci-dessus et observer la différence dans le comportement de
Coq.)
Exercice : 1 étoile (plus_id_exercise)
Effacer «Admitted.» et faire la preuve.Theorem plus_id_exercise : forall n m o : nat,
n = m -> m = o -> n + m = m + o.
Proof.
Admitted.
☐
Comme nous l'avons vu dans les exemples précédents, la
commande Admitted demande à Coq de passer sur la preuve de ce
théorème et de l'accepter comme tel. Cela peut se révéler utile lors
du développement de preuves plus longues : on peut énoncer des faits
auxiliaires dont on espère se servir dans une argumentation plus
large, utiliser Admitted pour repousser leur preuve à plus tard et
continuer l'argumentation plus large, pour y revenir quand on est sûr
que cela fait sens ; on peut alors remplir les trous. Il faut néanmoins
faire attention: à chaque Admitted (ou admit), on laisse une porte
ouverte à toutes les absurdités possibles, qui peuvent ainsi
s'introduire dans le monde rigoureux et formellement vérifié de Coq !
On peut aussi utiliser la tactique rewrite avec un théorème
précédent plutôt qu'une hypothèse du contexte.
Theorem mult_0_plus : forall n m : nat,
(0 + n) * m = n * m.
Proof.
intros n m.
rewrite -> plus_O_n.
reflexivity. Qed.
Theorem mult_1_plus : forall n m : nat,
(1 + n) * m = m + (n * m).
Proof.
Admitted.
(1 + n) * m = m + (n * m).
Proof.
Admitted.
☐
Preuve par cas
Theorem plus_1_neq_0_firsttry : forall n : nat,
beq_nat (n + 1) 0 = false.
Proof.
intros n.
simpl. Abort.
La cause de ce comportement est que les définitions de
beq_nat et + commencent par un raisonnement par cas sur leur
premier argument. Mais ici, le premier argument de + est l'entier
inconnu n et l'argument de beq_nat est l'expression composée
n+1; aucun des deux ne se simplifie.
Il nous manque ici un moyen de considérer les différentes formes
possibles de n séparément. Si n est O, alors on peut calculer le
résultat final de beq_nat (n+1) 0 et vérifier que c'est en effet
false. De plus, si n=S n' pour un certain n', alors, bien qu'on
ne connaisse pas exactement le nombre n+1, on peut au moins calculer
qu'il commence par un S, ce qui suffit à calculer que, ici encore,
beq_nat (n+1) 0 renvoie false.
La tactique qui demande à Coq de considérer séparément les cas n=0
et n=S n' s'appelle destruct.
Theorem plus_1_neq_0 : forall n : nat,
beq_nat (n + 1) 0 = false.
Proof.
intros n. destruct n as [| n'].
reflexivity.
reflexivity. Qed.
Le destruct engendre deux sous-buts, que nous devons
ensuite prouver séparément pour que Coq reconnaisse que le théorème
est prouvé. (Il n'y a pas besoin de commande pour bouger d'un sous-but
à l'autre. Lorsque le premier sous-but est prouvé, il disparaît et on
se retrouve avec l'autre.) Dans cette preuve, chaque sous-but se
prouve facilement en une invocation de reflexivity.
L'annotation «as [| n']» s'appelle un motif d'introduction. Elle
dit à Coq comment nommer les variables introduites dans chaque
sous-but. En général, on écrit entre les crochets une liste de
listes de noms, séparés par |. Ici, la première composante est vide,
puisque le constructeur 0 est zéro-aire (il ne contient aucune
donnée). La seconde composant donne un seul nom, n', car S est un
constructeur unaire.
La tactique destruct peut s'utiliser avec n'importe quel type de
données inductif. Par exemple, utilisons-le pour démontrer que la
négation booléenne est involutive, i.e., qu'elle est sa propre
inverse.
Theorem negb_involutive : forall b : bool,
negb (negb b) = b.
Proof.
intros b. destruct b.
reflexivity.
reflexivity. Qed.
Observons que le destruct n'a ici pas de clause as,
parce qu'aucun de ses sous-cas n'a de variables à lier; il n'a donc
besoin de lier aucun nom. (On aurait aussi pu écrire as [|], ou as
[].) En fait, on peut omettre la clause as de n'importe quel
destruct, auquel cas Coq inventera des noms de variables
automatiquement. Bien que ce soit confortable, on peut arguer que
c'est une mauvaise pratique, les choix faits automatiquement par Coq
étant souvent contre-intuitifs.
Exercice : 1 étoile (zero_nbeq_plus_1)
Theorem zero_nbeq_plus_1 : forall n : nat,
beq_nat 0 (n + 1) = false.
Proof.
Admitted.
beq_nat 0 (n + 1) = false.
Proof.
Admitted.
☐
Encore des exercices
Exercice : 2 étoiles (fonctions booléennes)
Utiliser les tactiques apprises jusqu'ici pour démontrer le théorème suivant sur les fonctions booléennes.Theorem identity_fn_applied_twice :
forall (f : bool -> bool),
(forall (x : bool), f x = x) ->
forall (b : bool), f (f b) = b.
Proof.
Admitted.
Énoncer à présent, puis démontrer, un théorème
negation_fn_applied_twice similaire au précédent mais où la deuxième
hypothèse demande que la fonction f vérifie f x = negb x pour tout
x.
Theorem negation_fn_applied_twice :
forall (f : bool -> bool),
(forall (x : bool), f x = negb x) ->
forall (b : bool), f (f b) = b.
Proof.
intros f H b.
rewrite -> H. rewrite -> H. destruct b.
simpl. reflexivity.
simpl. reflexivity.
Qed.
Exercice : 2 étoiles (andb_eq_orb)
Démontrer le théorème suivant. (Vous pourrez avoir besoin de démontrer un ou deux lemmes auxiliaires.)Theorem andb_eq_orb :
forall (b c : bool),
(andb b c = orb b c) ->
b = c.
Proof.
Admitted.
Exercice : 3 étoiles (binaire)
Considérons une représentation différente, plus efficace, des entiers naturels utilisant un système binaire plutôt qu'unaire. C'est-à-dire, plutôt que de dire que chaque entier est soit zéro soit le successeur d'un entier, nous pouvons dire que chaque entier binaire est soit- zéro,
- le double d'un nombre binaire, ou
- un de plus que le double d'un entier binaire.
☐
Notation "x + y" := (plus x y)
(at level 50, left associativity)
: nat_scope.
Notation "x * y" := (mult x y)
(at level 40, left associativity)
: nat_scope.
Pour chaque symbole de notation en Coq on peut spécifier son
«niveau de priorité» et son «associativité». Le niveau de priorité
n peut être spécifié par les mots at level n et il aide à lever
des ambiguïtés dans les expressions composées. L'associativité
sert à lever des ambiguïtés dans les expressions contenant
plusieurs occurrences du même symbole. Par exemple, les paramètres
spécifiés ci-dessus pour + et * disent que l'expression
1+2*3*4 est un raccourci pour l'expression (1+((2*3)*4)). Coq
utilise les niveaux de priorité de 0 à 100 et les associativités
left, right et no.
Chaque symbole de notation en Coq n'est de plus actif que dans sa
portée de notation. Coq tente de deviner quelle portée
l'utilisateur a en tête, donc quand on écrit S (0*0), il devine
nat_scope, mais quand on écrit le type produit (le type des
n-uplets) bool * bool il devine type_scoe. Occasionnellement, il
faut aider Coq en utilisant la notation «pour cent» en écrivant
(x*y)%nat et il arrive que Coq affiche %nat pour indiquer la
portée d'une notation.
Les portées de notations s'appliquent aussi à la notation
numérique (3,4,5, etc...), de sorte qu'on peut parfois voir 0%nat,
qui signifie O, ou 0%Z, qui dénote l'entier relatif zéro.
Fixpoints et récursion structurelle
Fixpoint plus' (n : nat) (m : nat) : nat :=
match n with
| O => m
| S n' => S (plus' n' m)
end.
Quand Coq vérifie cette définition, il observe que plus' est
«décroissante en son premier argument». Cela signifie qu'on effectue
une récursion structurelle sur l'argument n -- i.e., que tous nos
appels récursifs portent sur des valeurs strictement plus petites que
n. Cela implique que tous les appels à plus' finissent par
terminer. Coq exige que l'un des arguments de chaque définition par
Fixpoint soit «décroissant».
Cette exigence est un aspect fondamental de la conception de Coq : en
particulier, elle garantit que toutes les fonctions définissables en
Coq terminent sur tous les arguments possibles. Néanmoins, l'«analyse
de décroissance» de Coq étant limitée, il est parfois nécessaire
d'écrire des fonctions de manière peu naturelle.
Exercice : 2 étoiles (décroissance)
Pour concrétiser un peu ce point, trouver un moyen d'écrire une définition Fixpoint sensée (par exemple, d'une fonction simple sur les entiers) qui termine vraiment sur tous les arguments, mais que Coq n'accepte pas à cause de cette restriction.
☐
This page has been generated by coqdoc