Skip to content
Snippets Groups Projects
tp2.mv 28.6 KiB
Newer Older
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.

Théo Zimmermann's avatar
Théo Zimmermann committed
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.
```