Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
TP2 : Premiers pas en Coq
=========================
Introduction
------------
Coq est un "assistant de preuve", ou un "prouveur interactif de théorèmes", c'est-à-dire un logiciel qui permet d'énoncer des théorèmes mathématiques dans un langage formel, compréhensible par l'ordinateur, similaire à un langage de programmation. Il permet ensuite de prouver ces théorèmes, notamment en utilisant des tactiques qui permettent d'effectuer des étapes de preuve. L'assistant de preuve interagit avec l'utilisateur en lui fournissant des informations sur l'état de la preuve en cours, notamment les buts à prouver et les hypothèses disponibles.
Les assistants de preuve sont des outils qui sont développés depuis les années 70. Ils ont beaucoup gagné en popularité dans les deux dernières décennies. Ils ont trois applications principales :
- la vérification de programmes critiques
- la vérification de preuves mathématiques
- l'enseignement de la logique et des mathématiques
Coq est l'un des assistants de preuve les plus célèbres et les plus utilisés. Il a été utilisé dans ces trois applications, mais la plus commune est la vérification de programmes. Par exemple, le compilateur CompCert est un compilateur C formellement vérifié en Coq, qui garantit que le code machine produit par le compilateur est équivalent au code source C. CompCert est écrit et prouvé correct en Coq, puis "extrait" automatiquement en OCaml, pour être compilé et exécuté.
### Utilisation de Coq ###
Le fichier courant est un fichier `.mv`, mélangeant syntaxe Markdown et bloc de code Coq.
Si vous utilisez l'environnement de travail fourni dans le cadre du TP, vous êtes actuellement en train de lire ce fichier dans un éditeur VS Code avec l'extension coq-lsp. Lorsque vous placez le curseur quelque part dans un bloc de code Coq, l'extension affiche dans le panneau de droite soit le résultat de la commande sous le curseur, soit (si vous êtes au milieu d'une preuve) l'état courant de la preuve en cours (les buts à prouver, les hypothèses disponibles, etc.).
Si vous souhaitez utiliser Coq en dehors de cet environnement, vous devez [installer Coq](https://coq.inria.fr/download) ainsi qu'une [interface utilisateur](https://coq.inria.fr/user-interfaces.html). La plupart des interfaces historiques (CoqIDE, Proof General pour l'éditeur Emacs, etc.) ont un mode d'utilisation différent de coq-lsp, où vous naviguez dans un fichier Coq (extension `.v`) en utilisant des boutons ou des raccourcis clavier pour voir l'état de la preuve. La disposition de l'interface est en revanche similaire.
### Commandes Coq ###
En Coq, une commande est formée d'un nom de commande (commençant par une majuscule), éventuellement suivie d'un ou plusieurs arguments, et terminée par un point. Exemples :
```coq
Check 0.
Check S.
Check nat.
Search nat.
Check 2 + 2 = 5.
Check forall x, exists y, x = 2 * y \/ x = 2 * y + 1.
```
Placez votre curseur successivement sur ces différentes lignes pour en voir le résultat dans le panneau de droite.
Quel est le but de la commande `Check` ?
On peut déjà remarquer à ce stade qu'en Coq, les termes ont des types, mais les types aussi ont des types.
### Preuve interactive en Coq ###
L'utilisateur déclare son intention d'effectuer une preuve avec une commande de la forme :
```coq
Lemma and_commut (A B : Prop) : A /\ B <-> B /\ A.
Proof.
```
On notera que cette commande donne un nom (ici: `and_commut`) au lemme, ce qui permettra de le référencer par la suite. On peut éventuellement remplacer `Lemma` par `Theorem` ou quelques autres mots-clés équivalents comme `Example`, `Proposition` ou `Fact`. On marquera le début de la preuve par la commmande `Proof` (très fortement recommandée même si facultative).
### Sous-buts et tactiques ###
Une fois le mode preuve lancé, le panneau de droite affiche en permanence un ou plusieurs sous-buts (**subgoals**) qu'il s'agit de démontrer. Ces sous-buts sont essentiellement des séquents de la déduction naturelle écrits verticalement: les hypothèses (nommées) sont en haut, et la conclusion figure en bas sous un trait. Dans la partie haute figurent également des déclarations de variables.
La preuve se fait à l'aide de **tactiques** (distinguées des commandes par une minuscule initiale), qui effectuent des transformations plus ou moins complexes sur le but courant. À chaque règle d'inférence de la déduction naturelle correspond une ou plusieurs tactiques (cf. plus bas), mais certaines tactiques permettent également d'effectuer des morceaux de preuve plus complexes, comme par exemple la résolution de contraintes linéaires en arithmétique de Presburger (tactique `lia`).
Les tactiques sont susceptibles d'engendrer de nouveaux sous-buts (correspondant aux prémisses), ou au contraire de faire disparaître le but courant (lorsque celui-ci est résolu). La preuve est terminée lorsqu'il n'y a plus de sous-but à démontrer. On doit alors utiliser la commande `Qed` (d'après *Quod erat demonstrandum*, le CQFD latin) pour conclure la preuve et repasser au mode *commande*. Voici par exemple une preuve complète pour l'énoncé précédent :
```coq
split.
- intros H.
destruct H.
split.
+ assumption.
+ assumption.
- intros H.
destruct H.
split; assumption.
Qed.
```
Placez le curseur successivement sur ces différentes lignes pour voir la preuve se dérouler dans le panneau de droite.
Lorsque les tactiques sont séparées par des points, Coq va alors les exécuter pas-à-pas. On peut aussi utiliser le `;` pour **chaîner** des tactiques. Ainsi `split; assumption` fait agir `assumption` sur les deux sous-buts créés par `split`.
Devant des tactiques, on peut éventuellement placer une **puce**, c'est-à-dire un des marqueurs `-` ou `+` ou `*`. Ces puces sont optionnelles mais aident grandement à hiérarchiser la preuve en cours en délimitant chaque sous-partie.
Enfin, les commentaires sont introduits par `(*` et terminés par `*)`, comme en OCaml.
Un *script de preuve*, sauvegardé dans un fichier avec l'extension `.v`, peut ensuite être *compilé*. Si les preuves que contient ce fichier sont correctes, un fichier compilé `.vo` est alors produit, qui permettra de recharger les définitions et lemmes depuis un autre fichier (commande `Require Import`). L'extension coq-lsp pour VS Code fournit également une commande `Coq LSP: Save .vo file for the current buffer` qui permet de compiler le fichier courant, y compris un fichier `.mv`.
Logique propositionnelle
------------------------
On pose l'existence de variables propositionnelles `A`, `B` et `C` :
```coq
Parameters A B C : Prop.
```
Nous allons prouver différents lemmes avec des tactiques que nous introduirons au fur et à mesure en les mettant en correspondance avec les règles de la déduction naturelle.
### Implication ###
L'implication en Coq s'écrit `->`. Par exemple, `A -> B` est la proposition "A implique B".
La tactique `intros` est équivalente à la règle `=>I` de la déduction naturelle. Elle permet d'introduire une hypothèse dans le contexte. Par exemple, si le but est `A -> B`, la tactique `intros H` introduit l'hypothèse `H : A` dans le contexte et remplace le but par `B` (remplacer `H` par un nom qui ne soit pas déjà utilisé dans le contexte). On pourra aussi passer plusieurs noms d'hypothèses à `intros` en les séparant par des espaces pour introduire plusieurs hypothèses à la fois (par exemple `intros H1 H2 H3`).
La tactique `apply` est équivalente à la règle `=>E` de la déduction naturelle. Elle permet d'utiliser une hypothèse ou un lemme pour prouver le but. Par exemple, si le but est `B` et que l'hypothèse `H : A -> B` est dans le contexte, alors `apply H` remplace le but par `A`.
La tactique `assumption` est équivalente à la règle `Axiom` de la déduction naturelle. Elle permet de conclure en utilisant une hypothèse du contexte. Par exemple, si le but est `B` et que l'hypothèse `H : B` est dans le contexte, alors `assumption` conclut le but.
Prouver en Coq les formules suivantes:
```coq
Lemma refl_impl : A -> A.
Proof.
Qed.
Lemma trans_impl : (A -> B) -> (B -> C) -> A -> C.
Proof.
Qed.
```
### Conjonction, disjonction et équivalence ###
La conjonction en Coq s'écrit `/\` : `A /\ B` est la proposition "A et B". La disjonction s'écrit `\/` : `A \/ B` est la proposition "A ou B". L'équivalence s'écrit `<->` : `A <-> B` est la proposition "A équivaut à B".
La tactique `split` est équivalente à la règle `/\I` de la déduction naturelle. Elle permet de prouver un `/\` en prouvant les deux côtés. Par exemple, si le but est `A /\ B`, alors `split` crée deux sous-buts, le premier avec le but `A` et le second avec le but `B`.
Les tactiques `left` et `right` sont équivalentes aux règles `\/I1` et `\/I2` de la déduction naturelle. Elles permettent de prouver un `\/` en prouvant l'un des deux côtés. Par exemple, si le but est `A \/ B`, alors `left` remplace le but par `A` tandis que `right` remplace le but par `B`.
Attention : il peut être tentant d'essayer `left` ou `right` dès qu'on rencontre un but de la forme `A \/ B`, mais il vaut généralement mieux retarder le plus tard possible cette décision de prouver la gauche ou la droite de la disjonction. Par exemple, il vaut mieux au préalable avoir extrait le maximum d'informations des hypothèses du contexte, notamment avec la tactique `destruct` que nous présentons ci-dessous. (Mais bon, vous savez déjà comment vous y prendre pour faire des preuves en maths, donc ce conseil a surtout pour but de vous rappeler de ne pas perdre vos bonnes intuitions et de ne pas vous laisser embarquer par une preuve Coq qui "semble" progresser. L'assistant de preuve "assiste", mais ne remplace pas votre intuition mathématique.)
La tactique `destruct` est équivalente aux règles `/\E` et `\/E` de la déduction naturelle. Elle permet de déstructurer un `/\` ou un `\/` dans le contexte. Par exemple, si le but est `C` et que l'hypothèse `H : A /\ B` est dans le contexte, alors `destruct H as (HA, HB)` introduit les hypothèses `HA : A` et `HB : B` dans le contexte sans changer le but. Si le but est `C` et que l'hypothèse `H : A \/ B` est dans le contexte, alors `destruct H as [HA | HB]` crée deux sous-buts, le premier avec l'hypothèse `HA : A` dans le contexte et le second avec l'hypothèse `HB : B` dans le contexte. Dans les deux sous-buts, le but est toujours `C`.
À noter que `<->` est défini comme une conjonction de deux implications, c'est-à-dire que `A <-> B` doit être lu comme `(A -> B) /\ (B -> A)`. Par conséquent, `split` peut être utilisé pour prouver un `<->` en prouvant les deux implications.
Prouver en Coq les formules suivantes:
```coq
Lemma comm_conj : A /\ B <-> B /\ A.
Proof.
Qed.
Lemma comm_disj : A \/ B <-> B \/ A.
Proof.
Qed.
Lemma assoc_conj : (A /\ B) /\ C <-> A /\ (B /\ C).
Proof.
Qed.
Lemma assoc_disj : (A \/ B) \/ C <-> A \/ (B \/ C).
Proof.
Qed.
```
### True et False ###
La proposition `True` est toujours vraie. La proposition `False` est toujours fausse. On peut prouver `True` avec la tactique `exact I` (équivalent de la règle d'introduction du ⊤ de la déduction naturelle). On peut remplacer n'importe quel but par `False` avec la tactique `exfalso` (équivalent de la règle d'élimination du ⊥ de la déduction naturelle). De même, on peut prouver n'importe quel but si le contexte contient une hypothèse `H : False` (`exfalso; assumption` fonctionnerait, mais on peut aussi utiliser `destruct H`). Nous expliquerons plus tard comment `destruct` fait pour être capable de gérer à la fois les conjonctions, les disjonctions et le cas de `False`.
Prouver en Coq les formules suivantes:
```coq
Lemma true_impl : True.
Proof.
Qed.
Lemma false_impl : False -> A.
Proof.
Qed.
Lemma true_conj : True /\ A <-> A.
Proof.
Qed.
Lemma false_conj : False /\ A <-> False.
Proof.
Qed.
Lemma true_disj : True \/ A <-> True.
Proof.
Qed.
Lemma false_disj : False \/ A <-> A.
Proof.
Qed.
```
### Négation ###
La négation `~A` est définie comme `A -> False`. Par conséquent, face à un but `~A`, `intros H` introduit l'hypothèse `H : A` dans le contexte et remplace le but par `False`. Face à un but `False`, vous pouvez utiliser une hypothèse ou un lemme dont la conclusion est une négation avec la tactique `apply`. Si vous préférez travailler sur un but dans lequel on a remplacé la négation par sa définition, vous pouvez utiliser la tactique `unfold "~"` (ou `unfold "~" in *` pour agir également dans le contexte).
Prouver en Coq les formules suivantes:
```coq
Lemma double_neg : A -> ~~A.
Proof.
Qed.
Lemma contra : (A -> B) -> ~B -> ~A.
Proof.
Qed.
```
La preuve suivante n'est pas très intuitive. Pour la trouver, on peut chercher la "seule voie" raisonnable à chaque étape de la preuve. Demandez un indice à vos voisins si vous bloquez trop longtemps sur cette preuve.
Attention à ne pas être tenté d'utiliser `destruct` sur une hypothèse de la forme `~(A \/ B)`. Cela aurait le malheur de fonctionner, mais en vous faisant perdre de l'information précieuse pour la suite de la preuve. Pour le moment, on réservera `destruct` aux hypothèses de la forme `A /\ B`, `A \/ B` ou `False` (or, `~(A \/ B)` n'est pas de cette forme à cause de la négation).
```coq
Lemma double_neg_excluded_middle : ~~(A \/ ~A).
Proof.
Qed.
```
### Quelques exercices supplémentaires ###
Notons que la tactique `unfold` peut aussi être utilisée pour remplacer `<->` par sa définition. Par exemple, `unfold "<->"` remplace le but `A <-> B` par `(A -> B) /\ (B -> A)`.
Jusqu'à présent, on a plutôt fait du raisonnement "en arrière" (du but, vers les hypothèses). Parfois, il peut être utile de raisonner "en avant". La tactique `apply` a une variante qui le permet : `apply ... in ...`. Par exemple, si le contexte contient les hypothèses `H : A -> B` et `HA : A`, alors `apply H in HA` remplace `HA : A` par `HA : B`.
Prouver en Coq les formules suivantes:
```coq
Lemma iso_curry : (A /\ B -> C) <-> (A -> B -> C).
Proof.
Qed.
Lemma distrib_conj : A /\ (B \/ C) <-> (A /\ B) \/ (A /\ C).
Proof.
Qed.
Lemma distrib_disj : A \/ (B /\ C) <-> (A \/ B) /\ (A \/ C).
Proof.
Qed.
Lemma distrib_conj_impl : (A -> (B /\ C)) <-> (A -> B) /\ (A -> C).
Proof.
Qed.
Lemma distrib_disj_impl : ((A \/ B) -> C) <-> (A -> C) /\ (B -> C).
Proof.
Qed.
```
Programmation fonctionnelle en Coq
----------------------------------
Laissons de côté les preuves pour l'instant. Pour pouvoir prouver des programmes corrects en Coq, encore faut-il pouvoir les définir. Par conséquent, Coq intègre un langage de programmation (fonctionnel), appelé Gallina, qui permet de définir des fonctions et des types de données.
Ce langage est très proche d'OCaml, mais il y a quelques différences. Par exemple, les fonctions sont définies avec `Definition` au lieu de `let`, les définitions récursives avec `Fixpoint` au lieu de `let rec` et les types sont définis avec `Inductive` au lieu de `type`. Il n'y a pas de restrictions sur les noms des constructeurs et les noms de variables, qui peuvent commencer aussi bien par une minuscule qu'une majuscule.
Mais la plus grande différence, à la fois avec OCaml et avec les autres langages de programmation que vous connaissez, est que Gallina n'est pas un langage "Turing-complet". Pour pouvoir définir une fonction en Gallina, il faut pouvoir prouver qu'elle termine. Le plus souvent, cela se fait en utilisant la récursion structurelle, ce qui veut dire que la fonction a un argument qui diminue (structurellement) à chaque appel récursif. Nous définirons des fonctions récursives structurelles dans la suite de ce TP. Mais commençons par quelques exemples de fonctions non récursives.
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
### Types énumérés ###
Une autre différence notable de Coq par rapport à d'autres langages de programmation est que Coq ne fournit quasiment aucune fonctionnalité "pré-définie". Par exemple, il n'y a pas de type `bool` prédéfini dans le langage (mais la bibliothèque standard en fournit une définition). Par conséquent, nous pouvons redéfinir nous-mêmes le type `bool` :
```coq
Module Bool.
Inductive bool : Type :=
| true : bool
| false : bool.
```
Nous pouvons ensuite définir des fonctions sur ce type par pattern-matching :
```coq
Definition negb (b : bool) : bool :=
match b with
| true => false
| false => true
end.
```
On pourra remarquer quelques différences syntaxiques avec l'équivalent OCaml :
```ocaml
type bool = True | False
let negb (b : bool) : bool =
match b with
| True -> False
| False -> True
```
En particulier, dans les clauses du `match`, le séparateur entre le motif et le corps est `=>` au lieu de `->`, et les `match` de Coq doivent toujours finir par un `end`.
Vous pouvez évaluer cette fonction sur des exemples en utilisant la commande `Eval compute` :
```coq
Eval compute in negb true.
Eval compute in negb false.
```
Placez le curseur sur chacune de ces lignes pour voir le résultat dans le panneau de droite.
Nous pouvons aussi prouver des propriétés sur cette fonction. Pour le moment, nous allons nous limiter à des propriétés triviales, mais nous verrons plus tard comment prouver des propriétés plus intéressantes.
```coq
Example negb_true : negb true = false.
Proof.
simpl.
reflexivity.
Qed.
Example negb_false : negb false = true.
Proof.
simpl.
reflexivity.
Qed.
```
Ces preuves contiennent deux nouvelles tactiques : `simpl` permet de simplifier le but en effectuant des étapes de calcul, et `reflexivity` permet de conclure lorsque le but est une égalité entre deux termes que Coq reconnaît comme égaux. En fait, `reflexivity` est capable de faire des étapes de calcul pour arriver à des termes égaux, donc dans ces preuves, `simpl` est inutile. (Vous pouvez le vérifier en supprimant les `simpl`).
Définir les fonctions `andb`, `orb` et `xorb` sur `bool` (se limiter à un seul `match` sur le premier argument) :
```coq
Definition andb (b1 b2 : bool) : bool (* := *)
(* Supprimer la ligne ci-dessous et écrire la définition de andb à la place *)
. Admitted.
```
Si votre définition est correcte, les tests suivants (qui correspondent à la table de vérité de `andb`) devraient passer :
```coq
Example test_andb1: (andb true true) = true.
Proof. simpl. reflexivity. Qed.
Example test_andb2: (andb true false) = false.
Proof. simpl. reflexivity. Qed.
Example test_andb3: (andb false true) = false.
Proof. simpl. reflexivity. Qed.
Example test_andb4: (andb false false) = false.
Proof. simpl. reflexivity. Qed.
```
```coq
Definition orb (b1 b2 : bool) : bool (* := *)
(* Supprimer la ligne ci-dessous et écrire la définition de orb à la place *)
. Admitted.
Example test_orb1: (orb true true) = true.
Proof. simpl. reflexivity. Qed.
Example test_orb2: (orb true false) = true.
Proof. simpl. reflexivity. Qed.
Example test_orb3: (orb false true) = true.
Proof. simpl. reflexivity. Qed.
Example test_orb4: (orb false false) = false.
Proof. simpl. reflexivity. Qed.
```
```coq
Definition xorb (b1 b2 : bool) : bool (* := *)
(* Supprimer la ligne ci-dessous et écrire la définition de xorb à la place *)
. Admitted.
Example test_xorb1: (xorb true true) = false.
Proof. simpl. reflexivity. Qed.
Example test_xorb2: (xorb true false) = true.
Proof. simpl. reflexivity. Qed.
Example test_xorb3: (xorb false true) = true.
Proof. simpl. reflexivity. Qed.
Example test_xorb4: (xorb false false) = false.
Proof. simpl. reflexivity. Qed.
```
Coq fournit une syntaxe simplifiée pour le pattern-matching sur les booléens. Par exemple, la définition de `negb` peut être simplifiée comme suit :
```coq
Definition negb' (b : bool) : bool :=
if b then false else true.
End Bool.
```
On remarquera qu'on a pu se servir du `if` alors que nous sommes en train de manipuler les booléens que nous avons nous-même définis, et pas ceux de la bibliothèque standard. Comment est-ce possible ? En fait, la syntaxe if ... then ... else ... est juste du sucre syntaxique pour du pattern-matching sur des types inductifs à deux constructeurs. Comme on peut le voir dans l'exemple suivant, le nom du type ou des constructeurs n'a pas d'importance :
```coq
Inductive toto : Type :=
| tata : toto
| tutu : toto.
Definition negt (t : toto) : toto :=
if t then tutu else tata.
Eval compute in negt tata.
```
### Types paramétrés ###
Un type paramétré bien connu est le type `option` d'OCaml :
```ocaml
type 'a option = None | Some of 'a
```
L'équivalent en Coq est le suivant :
```coq
Module Option.
Inductive option (A : Type) : Type :=
| None : option A
| Some : A -> option A.
```
En Coq, le polymorphisme est toujours explicite. `option` est donc une fonction qui prend un type en argument et renvoie un type :
```coq
Check option.
```
De même, `None` est une fonction qui prend le type `A` en argument et renvoie une valeur de type `option A` et `Some` est une fonction de deux arguments, le premier de type `A` et le second de type `option A`.
```coq
Eval compute in None bool.
Eval compute in Some bool true.
```
Pour éviter d'avoir à expliciter le type `A` à chaque fois que l'on utilise les constructeurs, on dit à Coq de considérer ces arguments comme implicites (syntaxe `{...}` ci-dessous) :
```coq
Arguments None {A}.
Arguments Some {A} _.
```
Désormais, on peut écrire `None` au lieu de `None bool` et `Some true` au lieu de `Some bool true`.
```coq
Eval compute in None. (* Coq ne sait pas encore quel sera la valeur de A *)
Eval compute in Some true.
```
Nous pouvons maintenant définir des fonctions par pattern-matching sur les valeurs de type `option` :
```coq
Definition with_default {A : Type} (default : A) (o : option A) : A :=
match o with
| None => default
| Some x => x
end.
Lemma with_default_some (A : Type) (default : A) (x : A) :
with_default default (Some x) = x.
Proof. simpl. reflexivity. Qed.
```
Définir la fonction `is_none` qui renvoie `true` si son argument est `None` et `false` sinon :
```coq
Definition is_none {A : Type} (o : option A) : bool (* := *)
(* Supprimer la ligne ci-dessous et écrire la définition de is_none à la place *)
. Admitted.
Example test_is_none_none (A : Type) : is_none (A := A) None = true.
Proof. simpl. reflexivity. Qed.
Example test_is_none_some (A : Type) (x : A) : is_none (Some x) = false.
Proof. simpl. reflexivity. Qed.
```
```
Définir la fonction `map_option` qui applique une fonction à l'intérieur d'un `option` :
```coq
Definition map_option {A B : Type} (f : A -> B) (o : option A) : option B (* := *)
(* Supprimer la ligne ci-dessous et écrire la définition de map_option à la place *)
. Admitted.
Example test_map_option_none (A B : Type) (f : A -> B) :
map_option f None = None.
Proof. simpl. reflexivity. Qed.
Example test_map_option_some (A B : Type) (f : A -> B) (x : A) :
map_option f (Some x) = Some (f x).
Proof. simpl. reflexivity. Qed.
```
Lorsqu'on définit des fonctions qui prennent d'autres fonctions en argument, comme `map_option` ci-dessus, il est souvent utile de pouvoir utiliser des fonctions anonymes. En Coq, on peut définir des fonctions anonymes avec la syntaxe `fun x => ...` :
```coq
Example test_map_option_double (x : nat) :
map_option (fun y => y + y) (Some x) = Some (x + x).
Proof. simpl. reflexivity. Qed.
End Option.
```
À noter que dans les langages de programmation (qu'ils soient principalement fonctionnels ou non), les fonctions anonymes sont parfois aussi appelées lamdbas (ou plus préciément des λ-abstractions) en référence au lambda-calcul.
### Types récursifs ###
Le type des entiers n'est pas non plus prédéfini en Coq (mais à nouveau, la bibliothèque standard en fournit une définition). Nous pouvons le définir comme suit :
```coq
Module NatDef.
Inductive nat : Type :=
| O : nat
| S : nat -> nat.
End NatDef.
```
Cette représentation des entiers naturels utilise le symbole `S` (pour "successeur") pour représenter l'entier `n+1` à partir de l'entier `n`. Par exemple, `S (S (S O))` représente l'entier `3`. Elle est dite "unaire", car cela revient à compter avec des bâtons (le nombre de `S`). Il est également possible de représenter les entiers en base 2, qui est la base préférée des ordinateurs, mais la représentation unaire est plus simple à manipuler dans les définitions et les preuves.
La bibliothèque standard de Coq fournit une notation permettant d'écrire `0`, `1`, `2`, etc. au lieu de `O`, `S O`, `S (S O)`, etc. :
```coq
Check 0.
Check 1.
Check (S (S 0)).
```
Définir la fonction `pred` qui calcule le prédécesseur d'un entier :
```coq
Definition pred (n : nat) : nat (* := *)
(* Supprimer la ligne ci-dessous et écrire la définition de pred à la place *)
. Admitted.
Example test_pred_0 : pred 0 = 0.
Proof. simpl. reflexivity. Qed.
Example test_pred_Sn (n : nat) : pred (S n) = n.
Proof. simpl. reflexivity. Qed.
```
Nous pouvons définir des fonctions par récursion structurelle sur les entiers. Par exemple, la fonction `add` qui calcule la somme de deux entiers peut être définie comme suit :
```coq
Fixpoint add (n m : nat) : nat :=
match n with
| O => m
| S n' => S (add n' m)
end.
```
Si besoin, on peut préciser à Coq quel est l'argument qui diminue structurellement à chaque appel récursif :
```coq
Fixpoint add' (n m : nat) {struct n} : nat :=
match n with
| O => m
| S n' => S (add' n' m)
end.
```
Coq infère cette information automatiquement lorsqu'on ne la fournit pas explicitement, mais il n'y arrive pas toujours.
Définir la fonction `mul` qui calcule le produit de deux entiers :
```coq
Fixpoint mul (n m : nat) : nat (* := *)
(* Supprimer la ligne ci-dessous et écrire la définition de mul à la place *)
. Admitted.
Example test_mul_0_0 : mul 0 0 = 0.
Proof. simpl. reflexivity. Qed.
Example test_mul_10_10 : mul 10 10 = 100.
Proof. simpl. reflexivity. Qed.
```
Définir la fonction `fact` qui calcule la factorielle d'un entier :
```coq
Fixpoint fact (n : nat) : nat (* := *)
(* Supprimer la ligne ci-dessous et écrire la définition de fact à la place *)
. Admitted.
Example test_fact_0 : fact 0 = 1.
Proof. simpl. reflexivity. Qed.
Example test_fact_3 : fact 3 = 6.
Proof. simpl. reflexivity. Qed.
```
### Limites de la récursion structurelle ###
On souhaite définir la fonction `fib` qui calcule le `n`-ième nombre de Fibonacci.
La définition suivante ne fonctionne pas :
```coq
Fixpoint fib (n : nat) : nat :=
match n with
| 0 => 0
| 1 => 1
| S (S n') => fib n' + fib (S n')
end.
```
Trouver une manière de la corriger (toujours sans utiliser de fonction auxiliaire récursive), puis proposer une version alternative `fib'` qui utilise une fonction auxiliaire récursive.
```coq
Example test_fib_0 : fib 0 = 0.
Proof. simpl. reflexivity. Qed.
Example test_fib_10 : fib 10 = 55.
Proof. simpl. reflexivity. Qed.
Definition fib' (n : nat) : nat (* := *)
(* Supprimer la ligne ci-dessous et écrire la définition de fib' à la place *)
. Admitted.
Example test_fib'_0 : fib' 0 = 0.
Proof. compute. reflexivity. Qed.
Example test_fib'_10 : fib' 10 = 55.
Proof. compute. reflexivity. Qed.
```
On rappelle que la fonction Ackermann est définie comme suit :
- `A(m, n, 0) = m + n`
- `A(m, 1, k + 1) = m`
- `A(m, n + 1, k + 1) = A(m, A(m, n, k + 1), k)`
Cette définition présente l'inconvénient de ne pas être totale (pas de cas pour `A(m, 0, k + 1)`). On peut la rendre totale en ajoutant :
- `A(m, 0, 1) = 0`
- `A(m, 0, k + 2) = 1`
Auquel cas, la deuxième équation devient redondante et peut être supprimée.
Définir la fonction `Ack` qui calcule la fonction d'Ackermann. On commencera par remarquer que définir `Ack` de manière naïve ne fonctionne pas. Pour contourner cette difficulté, on pourra remarquer que la fonction d'Ackermann peut aussi s'exprimer ainsi :
`A(m,n,k+1)` = «appliquer `n−1` fois `n ↦ A(m,n,k)` à `m`»
Par conséquent, commencer par définir la fonction `iterate` qui applique une fonction `f` `n` fois à un argument `x`, puis s'en servir pour définir `Ack` :
```coq
Fixpoint iterate {A : Type} (f : A -> A) (n : nat) (x : A) {struct n} : A (* := *)
(* Supprimer la ligne ci-dessous et écrire la définition de iterate à la place *)
. Admitted.
Example test_iterate_0 (A : Type) (f : A -> A) (x : A) :
iterate f 0 x = x.
Proof. simpl. reflexivity. Qed.
Example test_iterate_1 (A : Type) (f : A -> A) (x : A) :
iterate f 1 x = f x.
Proof. simpl. reflexivity. Qed.
Example test_iterate_2 (A : Type) (f : A -> A) (x : A) :
iterate f 2 x = f (f x).
Proof. simpl. reflexivity. Qed.
```
```coq
Fixpoint Ack (m n k : nat) : nat (* := *)
(* Supprimer la ligne ci-dessous et écrire la définition de Ack à la place *)
. Admitted.
Example test_Ack_2_5_0 (m n : nat) : Ack 2 5 0 = 7.
Proof. simpl. reflexivity. Qed.
Example test_Ack_2_5_1 (m n : nat) : Ack 2 5 1 = 10.
Proof. simpl. reflexivity. Qed.
Example test_Ack_2_5_2 (m n : nat) : Ack 2 5 2 = 32.
Proof. simpl. reflexivity. Qed.
```