TAPFA - Partie Coq
- Auteurs:
Jean-Paul Bodeveix (resp. UE), Erik Martin-Dorel, Pierre Roux
- Date:
- L3 Info, Semestre 6, Année 2021-2022
Supports de ce cours :
Aperçu des fonctionnalités de Coq
Présentation du logiciel Coq
Implémenté en OCaml
Initié par Thierry Coquand et Gérard Huet, et développé par Inria depuis 1984 (la dernière version stable étant, lors de l'écriture de ce document, Coq 8.15.0).
Comprend un langage fonctionnel pur, statiquement typé, et très expressif (on détaillera cet aspect dans quelques minutes), muni d’un environnement de preuve interactive (vu au Cours n°2).
Coq a une grande communauté d’utilisateurs. Projets notables :
CompCert : compilateur C99 complètement prouvé en Coq [Inria]
C-CoRN : bibliothèque d’analyse constructive [à Nijmegen]
Preuve formelle du théorème des 4 couleurs [G. Gonthier, B. Werner]
MathComp : bibliothèque d’algèbre et preuve formelle du théorème de Feit–Thompson (la preuve papier fait \({>}300\) pages) [Inria, Microsoft Research]
- Vérification de systèmes critiques (voir par exemple :
FiatCrypto : "Simple High-Level Code For Cryptographic Arithmetic: With Proofs, Without Compromises" (Implementations from our library were included in BoringSSL to replace existing specialized code, for inclusion in several large deployments for Chrome, Android, and CloudFlare.)
Ouvrage (publié chez Springer et téléchargeable en français) :
Coq'Art: Interactive Theorem Proving and Program Development
Caractéristiques intéressantes de Coq
langage fonctionnel pur proche d’OCaml
terminaison garantie (impossible d’écrire let rec f x = f x)
typage fort et très expressif (on peut définir des listes à taille fixée, des fonctions à arité variable, etc.)
- langage logique (expression de formules quantifiées, etc.)
forall n : nat, n > 0 -> exists m : nat, S m = n
environnement de preuve (assistance à la construction de preuve)
- vérification automatique de la correction de ces preuvesvérification de preuve = vérification de type
- tactiques utilisateur pour automatiser les preuves :
tauto
pour la logique des propositions,firstorder
pour la logique du premier ordre,lia
pour l’arithmétique linéaire sur les entiers (2 * (n + 1) - n > n
)etc.Require Import Lia. (* pour utiliser la tactique "lia" *)
Rappel : installation de l’environnement de TP
Environnement recommandé
Coq 8.15 + Emacs + ProofGeneral + Company-Coq [cf. tutoriel]
Carte de référence / raccourcis clavier [cf. Moodle]
Dictionnaire OCaml/Coq [cf. Moodle ; autorisé à l'examen a priori]
Tutoriel d’installation
avec OPAM, sous GNU/Linux, macOS ou Windows 10 :
à installer avant le 1er TP TAPFA-Coq
avant cela, jsCoq peut être utilisé dans votre navigateur :
n’hésitez pas à poser des questions sur Moodle
Utilisations possibles de Coq
Synthèse du type d’une expression :
Évaluation d’expressions (calcul) :
Implantation de fonctions et vérification de leurs propriétés :
Definition f x := x + 2.forall x y : nat, x < y -> f x < f y(* On ne détaille pas pour le moment le script de preuve => Cours 2 *) Qed.forall x y : nat, x < y -> f x < f y
Définition de sous-types :
Definition Mod3 := {x : nat | x < 3}.
Synthèse de fonctions à partir d’une preuve de sa spécification :
forall n : nat, n > 0 -> {m : nat | S m = n}(* On ne détaille pas pour le moment le script de preuve *) Defined. Require Extraction. (* Génération automatique de code OCaml à partir du code Coq prouvé *)forall n : nat, n > 0 -> {m : nat | S m = n}
Motivations pour utiliser Coq
Positionnement de Coq par rapport aux types abstraits
Par rapport à PFITA
explicitation des axiomes dans la spécification des types abstraits
preuve formelle que l'implémentation satisfait bien la spécification
compilation et vérification modulaire possible
(implémentation et vérification d'un composant utilisateur indépendemment de l'implémentation du composant utilisé)
donc : plus besoin de tests unitaires ou d'intégration...
mais : Toujours besoin de validation de l'implémentation par rapport aux exigences attendues (qui peuvent différer des exigences exprimées dans le cahier des charges !)
Voir aussi : la notion de V & V
Exemple de vérification modulaire
Définition d'une signature
Module Type tPickGt. Parameter pick : nat -> nat. Axiom pick_gt : forall n, pick n > n. End tPickGt.
Implantation de la signature
Module PickGt <: tPickGt. (* vérif conformité sans masquage *) Definition pick n := S n.forall n : nat, pick n > nauto. Qed. End PickGt.forall n : nat, pick n > n
Utilisation d'un module respectant la signature
Module Use (P : tPickGt). Definition num := P.pick O.num > 0apply P.pick_gt. Qed. End Use.num > 0
Instantiation du module paramétré
Module M := Use(PickGt).
Introduction à Coq
Types et expressions
Exemples d’expressions Coq et leur type
valeur/programme |
type |
type de type |
… |
---|---|---|---|
|
|
Set |
… |
|
|
Set |
… |
|
|
Set |
… |
Exemples avancés d’expressions Coq et leur type
valeur/programme |
type |
type de type |
… |
---|---|---|---|
|
|
|
… |
"preuve de |
|
|
… |
"(1; preuve de |
|
|
… |
(* Prop est le type des formules logiques *)
Remarques
Prop
est le type des formules logiques ;à ne pas confondre avec
bool
, le type des booléens.
Les deux ont leur utilité :
les prédicats renvoyant un
bool
correspondent à des "prédicats calculables" (par exemple un algorithme décidant si un entier est pair ou pas) : on peut déterminer leur résultat par un calcul ;les prédicats renvoyant un
Prop
correspondent à des "prédicats logiques" (définis par une formule) : on peut prouver leur véracité par un raisonnement.
Le type des listes : comparaison OCaml / Coq
OCaml |
Coq |
---|---|
|
|
|
|
notation spéciale, mais familière |
|
Éléments de syntaxe
Les mots "terme" et "expression" sont synonymes.
- Rappel :En Coq, tout terme bien formé a un type (y compris les types !) ;contrairement à OCaml, les termes/expressions et les types ne forment pas deux "collections disjointes" ;donc la grammaire des termes ci-dessous contient aussi Type, Set, Prop :
terme ::= Type | Set | Prop "sortes" (= types de type)
| var "variable"
| ( terme )
| terme terme "application"
| terme op terme "opérateur infixe"
| fun var : terme ⇒ terme "abstraction"
| let var := terme in terme "définition locale"
| if terme then terme else terme
| terme -> terme "type flèche" (= type des fonctions)
Exemples
Que renvoient les commandes suivantes ?
Definition f1 := fun x => x + 1.Definition double f := fun (x : nat) => f (f x).Definition double2 (f : nat -> nat) := fun x => f (f x).
Cette dernière définition est refusée car en Coq, les paramètres de généricité sont explicites. Pas de synthèse automatique de leur nom ; il faut expliciter ces arguments.
Généricité
Une fonction générique prend un type en paramètre.
Exemple de définition d'une fonction générique : la fonction identité.
Definition id := fun (T : Type) => fun (x : T) => x. (* ou *) Definition id_v2 := fun (T : Type) (x : T) => x. (* ou *) Definition id_v3 (T : Type) := fun (x : T) => x. (* ou *) Definition id_v4 (T : Type) (x : T) := x. (* ou *) Definition id_v5 T (x : T) := x.
\(\leadsto\) ces fonctions ont toutes 2 arguments !
Utilisation d’une fonction générique
Généricité et typage
Type d’une fonction générique
En OCaml : id : 'a -> 'a \(\leadsto\) pour tout type 'a, id prend un argument de type 'a et retourne un résultat de type 'a
En Coq :
id : forall a, a -> a
\(\leadsto\) la quantification est expliciteextension du langage des termes :
terme ::= Type | Set | Prop
| var
| ( terme )
| terme terme
| terme op terme
| fun var : terme ⇒ terme
| let var := terme in terme
| if terme then terme else terme
| terme -> terme (type d'une fonction)
| forall var : terme, terme (type d'une fonction générique)
- Comme on le verra par la suite,
t1 -> t2
est en faitune syntaxe alternative (plus intuitive) de "forall _ : t1, t2
".
Inférence d'arguments
Dans le terme id bool true
ci dessus, Coq pourrait deviner
automatiquement le premier argument bool
puisque c'est le type
du deuxième argument true
d'après le type de id
. Il est
effectivement possible d'écrire
Coq remplace automatiquement le _
par la seule valeur possible bool
.
On peut ainsi remplacer tout sous terme par un trou _
et laisser Coq
tenter de le remplir. Coq retourne une erreur quand il ne trouve pas
de solution canonique.
Généricité et arguments implicites I
Les remarques qui suivent ne sont pas "exigibles à l'examen" mais permettront d'avoir plus de confort, notamment en TP, en évitant de devoir expliciter tous les paramètres de généricité lors de l'appel d'une fonction polymorphe.
Les arguments implicites permettent d'écrire id true
au lieu de id _ true
.
Redéfinissons la fonction id après activation des arguments implicites
Set Implicit Arguments. (* au début du fichier en principe *) Definition id' (T : Type) (x : T) := x.(* même code que *)(* mais *)(* id' : forall [T : Type], T -> T *)
(\(\leadsto\) noter le [T] au lieu de T)
L'argument T est implicite : il peut être inféré à partir de l'argument d'après.
D'où une syntaxe concise à la OCaml :
Set Printing All. (* affiche tout, dont les arguments implicites *)
Le @
indique que tous les arguments de id'
sont explicités.
Grâce aux arguments implicites, entrer id' true
est equivalent à entrer
Unset Printing All.
Généricité et arguments implicites II
En OCaml :
let id x = x;;
id 0;; (* : int = 0 *)
id true;; (* : bool = true *)
En Coq :
Set Implicit Arguments.
Definition id T (x:T) := x.
Check id 0. (* id 0 : nat *)
Check id true. (* id true : bool *)
Paramètres de généricité : un plus gros exemple
En OCaml :
let comp f g x = g (f x);;
val comp : (’a -> ’b) -> (’b -> ’c) -> ’a -> ’c = <fun>
comp (fun x -> x + 1) (fun x -> x * 2) 3;;
\(\leadsto\) ?
En Coq :
Pas de synthèse du nom des paramètres de généricité : il faut déclarer ces paramètres de généricité lors de la définition.
(* Set Implicit Arguments. (* déjà fait auparavant *) *) Definition comp T1 T2 T3 (f:T1->T2) (g:T2->T3) (x : T1) : T3 := g (f x). (* ou *) Definition comp_v2 T1 T2 T3 (f:T1->T2) (g:T2->T3) (x : T1) := g (f x). (* ou *) Definition comp_v3 T1 T2 T3 (f:T1->T2) (g:T2->T3) x := g (f x). (* ou *) Definition comp_v4 T1 T2 T3 f (g:T2->T3) (x:T1) := g (f x). (* ou *) Definition comp_v5 T1 T2 T3 (f:T1->T2) g (x:T1) : T3 := g (f x).
Exercices
Ecrire la fonction mult2 retournant le double de son argument.
Definition mult2 := (* todo *)0.
- Supposons défini le prédicat
leq : nat -> nat -> bool
, retournanttrue
si son 1er argument est inférieur à son second.Définir la fonction max retournant le plus grand de ses 2 arguments.En déduire la fonction max3 retournant le plus grand de ses 3 arguments.Ecrire une variante max3' où la définition de max est locale.Parameter leq : nat -> nat -> bool. Definition max := (* todo *)0. Definition max3 := (* todo *)0. Definition max3' := (* todo *)0.
Quel est le type de l'expression suivante ?
Definition expr3 := fun (T:Type) (f:T -> T) x => f (f x). (* *)
Quel est le type de l'expression suivante ? (donner 2 syntaxes équivalentes)
Definition expr4 := fun (T:Type) => T -> T.
Quel est le type de l'expression suivante ?
Definition expr5 := fun (T:Type) (x:T) => x=x.
Écrire une fonction de type
forall a b : Type, a -> b -> a
Definition expr6 := (* todo *)0.
Écrire une fonction de type
forall a b : Type, a -> (a -> b) -> b
Definition expr7 := (* todo *)0.
8. Écrire une fonction de type
forall a b c : Type, (a -> b -> c) -> (a -> b) -> a -> c
Definition expr8 := (* todo *)0.
Écrire une fonction de type
forall a b c : Type, (a -> c) -> (a -> b -> c)
Definition expr9 := (* todo *)0.
10. Écrire une fonction de type
forall a b c : Type, (a -> b -> c) -> b -> a -> c
Definition expr10 := (* todo *)0.
Écrire une fonction de type
forall a b : Type, (a -> a -> b) -> a -> b
Definition expr11 := (* todo *)0.
Peut-on écrire une fonction de type
forall a b : Type, a -> b
?
(* En OCaml :
let exemple =
*)
(* En Coq ? *)
Retour sur ->
et forall
T1 -> T2
correspond :
au type des fonctions de
T1
dansT2
, c’est-à-direau type des fonctions qui associent à tout
x:T1
un élément deT2
,que l’on peut aussi écrire
forall (x : T1), T2
forall
est une notation :
généralisant
->
,utile lorsque le type d’arrivée
T2
dépend du terme fournix
.
Exemples de types définissables en Coq
forall (T : Type), T -> T
forall (p q : nat), q <> 0 -> nat
forall (b : bool), if b then nat else bool * bool
La correspondance de Curry–Howard : double lecture
Notation |
Lecture fonctionnelle |
Lecture logique |
---|---|---|
|
|
|
aucun |
le type |
|
|
type des fonctions de |
l'implication logique |
|
f est une fonction de |
f preuve de P => Q |
f transforme une "preuve" de |
Types inductifs
Définition d’un type inductif paramétré par a1,...,am.
En OCaml :
type ('a1, ..., 'am) t = (* m paramètres de généricité *)
| C1 of t1_1 * ... * t1_n1 (* constructeur C1 d'arité n1 *)
| ...
| Ck of tk_1 * ... * tk_nk (* kième constructeur *)
En Coq :
Inductive t a1 ... am :=
| C1 (p1 : t1_1) ... (pn1 : t1_n1)
| ...
| Ck (p1 : tk_1) ... (pnk : tk_nk). (* ne pas oublier le point *)
Analyse par cas
En OCaml :
match x with
| C1 (x1, ..., xn1) -> ...
| ...
| Ck (x1, ..., xnk) -> ...
En Coq :
match x with
| C1 x1 ... xn1 => ...
| ...
| Ck x1 ... xnk => ...
end
Différences :
Les constructeurs sont typiquement "curryfiés" en Coq.
Les
->
sont remplacées par=>
Il y a un
end
en plus (pratique)Sinon c'est la même syntaxe :-)
Exemples de types inductifs
Type de données prédéfini dans Coq :
Inductive entier_nat := Zero | Succ (p : entier_nat). Inductive liste T := Vide | Cons (x : T) (l : liste T).
Eléments d’un type inductif :
Definition liste1 := @Cons entier_nat (Succ Zero) (Vide entier_nat).
Fonctions récursives
Fixpoint add n m := match n with | Zero => m | Succ p => Succ (add p m) end. Fixpoint long T (l : liste T) := match l with | Vide _ => Zero | Cons x r => Succ (long r) end.
Est-ce normal d'écrire Cons x r => Succ (long r)
?
Précisons :
Le constructeur
Cons
a trois arguments,La fonction
long
a 2 arguments...Mais on avait activé les arguments implicites :
Set Implicit Arguments
.On pourrait néanmoins écrire en étant pleinement explicite :
Fixpoint long_v2 T (l : liste T) := match l with | Vide _ => Zero | @Cons _ x r => Succ (@long_v2 T r) end.
Remarque: Coq n'est pas limité aux fonctions primitives récursives. Par exemple, la fonction d'Ackermann peut être définie comme ci-dessous et il serait possible de démontrer qu'elle est identique à la définition usuelle:
Fixpoint pow (f:nat->nat) n a := match n with 0 => a | S p => pow f p (f a) end. Fixpoint ack a b : nat := match a with | 0 => S b | S a => pow (ack a) (S b) 1 end.
Schémas inductifs
Preuve par récurence sur les entiers naturels (construits avec Zero, Succ)
Est-ce que la fonction suivante (ou son type) vous rappellent quelque chose ?
Fixpoint entier_nat_ind (P : entier_nat -> Prop)
(fZero : P Zero) (fSucc : forall n : entier_nat, P n -> P (Succ n))
(n : entier_nat) : P n :=
match n with
| Zero => fZero
| Succ n => fSucc n (entier_nat_ind P fZero fSucc n)
end.
qui a pour type :
Il s'agit du "Principe d’induction" pour les entiers naturels, généré automatiquement par Coq.
(Et il se trouve que le "code" qui implémente ce principe correspond à
l'itérateur le plus général sur le type entier_nat
:)
Le type en question est donc :
forall P : entier_nat -> Prop, P Zero ->
(forall n : entier_nat, P n -> P (Succ n))
-> forall n : entier_nat, P n.
Un peu d’intuition sur ce principe d’induction
Hypothèses :
pZero : preuve de P Zero
pSucc : preuve de \(\forall n,\ P n \implies P (n+1)\)
Conclusion :
\(\forall n,\ P n\)
On peut l'écrire sous la forme d'une "Règle d'inférence" :
(pZero : P Zero) (pSucc : forall n : entier_nat, P n -> P (Succ n))
--------------------------------------------------------------------
entier_nat_ind P pZero pSucc : forall n : entier_nat, P n
L'effet domino
Une petite image vaut mieux qu'un long discours :
Le type inductif des listes (prédéfini)
Require Import List. Import ListNotations. (* Set Implicit Arguments. (* déjà fait auparavant *) *)
Fixpoint list_ind T (P : list T -> Prop)
(pNil : P nil) (pCons : forall x l, P l -> P (x :: l)) l
: P l :=
match l with
| [] => pNil
| x :: r => pCons x r (@list_ind T P pNil pCons r)
end.
Généré automatiquement !
Permet la "preuve par induction" sur les listes (construites avec []
et ::
)
Règle d'inférence du schéma inductif des listes
(pNil : P []) (pCons : forall x l, P l -> P (x :: l))
------------------------------------------------------
list_ind P pNil pCons : forall l, P l
Ici, on a omis les types implicites (le param. de généricité T) par simplicité.
Correspondance entre les types et les formules logiques
La correspondance de Curry–Howard : double lecture
Notation |
Lecture fonctionnelle |
Lecture logique |
---|---|---|
|
|
|
aucun |
le type |
|
|
type des fonctions de P dans Q |
l'implication logique |
|
f est une fonction de P dans Q |
f preuve de P => Q |
f transforme une "preuve" de P en une "preuve" de Q |
||
|
type générique de l'identité |
exemple de tautologie |
|
fonction associant à chaque entier une preuve |
quantification universelle |
|
fonction dont le type d'arrivée dépend de l'argument |
∀x∈X, P x |
Exemple de types inductifs : propositions logiques
La Conjonction
Inductive and (P Q : Prop) : Prop :=
Conj (p : P) (q : Q).
-->
(p : P) (q : Q)
--------------------
Conj p q : P /\ Q
La Disjonction
Inductive or (P Q: Prop): Prop :=
| Or1 (p: P)
| Or2 (q: Q).
-->
p : P q : Q
----------------- -----------------
Or1 p : P \/ Q Or2 q : P \/ Q
La proposition True
Inductive True : Prop := I. --> ----------
I : True
La proposition False
Inductive False : Prop := . --> pas de preuve de `False` !
Et la quantification existentielle ?
Inductive ex T (P: T -> Prop) :=
ex_intro (t: T) (p: P t).
-->
(t : T) (p : P t)
----------------------------------------
@ex_intro T P t p : exists x : T, P x
À suivre au prochain cours
Retour sur la correspondance de Curry-Howard
- Présentation des "tactiques Coq"pour construire une preuve interactivement/semi-automatiquement !