• Aucun résultat trouvé

6.2.1 Généralités

Coq est un système d’aide à la preuve basé sur le Calcul des Constructions Inductives (CCI), qui est un λ-calcul typé dans lequel des types sont des termes comme les autres. Il fournit des mécanismes pour écrire

des définitions et pour faire des preuves formelles.

Le système Coq est un programme interactif permettant à l’utilisateur de construire des preuves. Cela permet de :

1. Définir des termes, c’est-à-dire les objets (mathématiques) du discours, comme les entiers, les listes etc., et des propriétés sur ces objets puisque celles-ci sont des types et que types et termes ne sont pas différenciés ;

2. Construire des démonstrations de ces propriétés (qui sont également des termes) à l’aide d’un langage de tactiques permettant de fabriquer interactivement un terme d’un type donné ;

3. Vérifier les preuves ainsi construites, en demandant au programme de les typer.

Le mécanisme de construction des preuves n’a pas besoin d’être certifié puisque le noyau du système Coq, c’est-à-dire la fonction de typage vérifie les preuves. Le noyau fait en revanche l’objet de certifications et de vérifications [22]. Ajoutons que la représentation concrète des preuves a d’autres avantages : par exemple, elle rend possible l’écriture d’un vérificateur de type indépendant de Coq, et elle autorise également l’em-ploie de toutes sortes d’outils pour engendrer des preuves que Coq pourra vérifier a posteriori [107].

Tous les exemples donnés dans la syntaxe Coq seront dans une police verbatim et seront en général suivis de la réponse du système. Les différents exemples de ce chapitre peuvent êtres tapés directement dans Coq. Il n’y a pas d’inférence de type en Coq, celle-ci n’étant pas décidable2. Il est cependant laborieux de devoir annoter toutes les définitions par des informations de type quand celles-ci semblent pouvoir être aisément déduites du contexte. Coq propose donc un mécanisme simple d’inférence, qui, bien sûr, peut échouer. Coq fournit également un mécanisme d’arguments implicites et il s’efforce aussi de les inférer.

Nous nous intéressons donc, dans cette section, au système d’aide à la preuve Coq. Cette section a pour objectif de faire découvrir au lecteur ce système à l’aide d’une rapide description. Bien sûr, cette section n’a pas pour vocation de remplacer la documentation accompagnant le système. Le lecteur pourra donc consulter en complément : le tutoriel et le livre [30] pour une introduction plus graduelle ainsi que le manuel de référence3pour une description formelle et exhaustive de Coq.

6.2.2 Assistant de preuves

L’objectif premier de Coq est d’être un assistant de preuves, et donc de permettre de formaliser le rai-sonnement mathématique. Prenons un énoncé basique :∀A, A ⇒ A. Cet énoncé, qui se lit «pour toute

propositionA(c’est-à-dire objetAde type Prop),Aimplique A», peut être directement transcrit dans le système Coq comme suit :

2Cela correspondrait à un «prouveur» automatique «universel», ce qui est rigoureusement impossible.

85 6.2. PRÉSENTATION SUCCINTE DE COQ

Lemma easy : A : Prop , A→A .

Proof .

Cette portion de script commence par le nom et l’énoncé de notre lemme, où Prop est l’ensemble des propositions logiques. Le système entre alors dans sa nature interactive qui permet de bâtir cette preuve étape par étape (mot clésProof). Pour cela, le système utilise des directives appelées tactiques. Celles-ci reflètent la structure de la preuve en déduction naturelle. Nous avons donc :

1 subgoal

============================

A : Prop , A→A

easy < i n t r o s .

En entrant la tactiquei n t r o s (introduire les hypothèses), le système affiche l’état courant du ou des buts à prouver : 1 subgoal A : Prop H : A ============================ A easy < apply H. Proof completed .

Il s’agit alors de trouver une preuve deAsous l’hypothèseA(cette hypothèse étant nomméeHtel queA

soit de typeProp). La tactique apply Hpermet d’appliquer cette hypothèse, ce qui termine la preuve. Il ne reste plus qu’à faire vérifier l’ensemble de la preuve au système Coq (vérification des types de tous les sous-termes) à l’aide de la tactiqueQed.

De nombreuses interfaces existent pour faciliter cette interaction, comme par exemple CoqIDE4. Il faut également noter que Coq met à la disposition de l’utilisateur une grande variété de tactiques. Ici, par exemple, pour un énoncé aussi simple, les tactiques de recherche automatique telles queautoou t r i v i a l auraient été suffisantes.

La représentation interne d’une preuve est unλ-terme. Le système logique sous-jacent à Coq est le CCI

(Calcul des Constructions Inductives) qui est unλ-calcul typé où les énoncés exprimables en Coq sont des

types du CCI. En application de l’isomorphisme de Curry-Howard [119], vérifier sit est bien une preuve valide de l’énoncéTconsiste à vérifier que le typeTest bien un type légal duλ-terme t. C’est ce qui est effectué par le système avec la tactiqueQed. Par exemple, si on demande à Coq quelle est la représentation interne du lemmeeasyà l’aide d’unP r i n t, nous obtenons :

easy = fun ( A : Prop ) (H : A ) ⇒H

: A : Prop , A→A

fun x : X⇒eest la notation Coq pour l’abstraction typée. Celle-ci est l’effet (d’après l’isomorphisme de Curry-Howard) de la tactiquei n t r o. Quand àapply, son effet est l’utilisation de la variable de contexte

H. De façon générale, une tactique contribue à construire petit à petit leλ-terme CCI de la preuve.

Notons que si l’on connaît à l’avance leλ-terme complet, on peut le donner directement sous la forme

par exemple d’uneD e f i n i t i o n, ce qui donne :

D e f i n i t i o n easy : A : Prop , A→A : = fun (A : Prop ) (H : A ) ⇒H

La quantification universelle∀x : X, T est appelé produit ou type dépendant, puisque, le plus souvent, le

corpsTdu produit dépend de la variablexde la tête du produit. Cette quantification n’étant pas restreinte, le CCI (le système Coq), est donc une logique d’ordre supérieur. Notons que la syntaxeA→Best un sucre syntaxique de Coq pour _ : A , B.

6.2.3 Langage de programmation

On peut également aborder Coq (et c’est ce qui nous intéressera le plus ici) depuis l’autre versant de l’iso-morphisme de Curry-Howard : considérer Coq non pas comme un système logique mais plutôt commme un

λ-calcul, c’est-à-dire un langage de programmation purement fonctionnel. Par exemple, notre lemmeeasy

devient une fonction d’identité sur les propositions logiques.

En tant que langage de programmation, il nous faut pouvoir définir des types de données que l’on ren-contre dans les langages de programmation. Cette définition se fait en Coq à l’aide de types inductifs. Le type des booléens s’obtient par la déclaration :

I n d u c t i v e b o o l : Set : = t r u e : b o o l | f a l s e : b o o l

Cette déclaration crée un nouveau type, nomméb o o l, ainsi que deux nouveaux constructeurs. L’annotation

Setpermet de désigner quel va être le type du type b o o l. C’est le type des objets calculatoires (à la dif-férence deProp qui est le type des objets logiques). De même on peut définir les entiers de Peano de la manière suivante :

I n d u c t i v e n a t : Set : = 0 : n a t | S : n a t→n a t .

0est le zéro etSle successeur d’unn a t. Un dernier exemple usuel est celui des listes paramétriques : I n d u c t i v e l i s t ( A : Set ) : Set : = n i l : l i s t A | cons : A→l i s t A→l i s t A

où la liste dépend d’un paramètreA. Ce paramètre sera fourni en arguments lors de l’utilisation de cette structure de données. Par exemple une liste d’entiers naturels peut être définie par :

I n d u c t i v e l i s t _ n a t : Set : = n i l _ n a t : l i s t _ n a t

| cons_nat : n a t→l i s t _ n a t→l i s t _ n a t

mais plus simplement parD e f i n i t i o n l i s t _ n a t : Set : = ( l i s t n a t ) .

Il faut noter que le système refuse certains inductifs (on parle d’une condition de positivité) dont la définition est syntaxiquement valide. Ces restrictions ont pour but d’assurer la cohérence du système du point de vue logique5, c’est-à-dire l’impossibilité de prouverP et ¬P . Par exemple :

I n d u c t i v e absurde : Set : = C : ( absurde→absurde )→absurde .

E r r o r : Non s t r i c t l y p o s i t i v e occurrence o f " absurde " i n

" ( absurde absurde ) absurde "

L’entrelacement des parties logiques (des propositions) et des parties purement calculatoires est pos-sible dans le système Coq. Cela permet notamment d’enrichir un terme calculatoire avec des pré- et post-conditions, ou encore d’utiliser une récursion bien fondée en justifiant la décroissance d’une mesure à chaque appel récursif.

Outre le besoin d’exprimer la spécification d’une fonction sous la forme de pré- et de post-conditions, les pré-conditions logiques vont également apporter une solution au problème de la définition des fonctions partielles. Considérons, par exemple, une fonction de prédécesseur sur les entiers naturelspred : n a t→n a t, qui n’est pas définie quand son argument est nul. On peut alors exprimer, par une pré-condition logique, le fait que cet argument doit être non nul si l’on veut utiliser cette fonction. Le type depreddevient alors

n : nat , n<>0 n a t. On notera que le type de l’argument n’est plus un type flèche (un produit anonyme)

mais un produit nommé parnafin de s’y référer dans l’assertion logique. La fonctionpredn’est alors plus définie en dehors du domaine de validité de l’assertion logique. La contre-partie est que l’on doit toujours fournir une preuve logique de non-nullité à chaque appel àpred.

Combiner pré- et post-conditions spécificie une fonction dans un style à la Hoare [149]. Ainsi, une fonction de typeA→B de pré-conditionP et de post-conditionQ correspond à la preuve constructive :

∀x : A, (P x) → ∃y : B, (Q x y). Ceci est exprimable en Coq à l’aide d’un type inductifs i g:

I n d u c t i f s i g (A : Set ) ( P : A→Prop ) : Set : =

e x i s t : x : A , ( P x )→( s i g A P ) .

qui s’écrit également avec un sucre syntaxique{ x : A | ( P x ) }. Une spécification complète en Coq de la fonction de prédécesseur entière s’écrit alors :

D e f i n i t i o n pred : n : nat , n<>0 { q : n a t | ( S q )= n }

Il ne reste alors qu’à donner au système unλ-terme répondant à cette spécification (un λ-terme ayant ce

type). On peut le construire par une interaction de tactiques notamment avec une de filtrage (commecase) pour prouver tous les cas des constructeurs.

Une solution plus simple est l’utilisation de la tactique r e f i n e qui permet de donner directement le terme Coq correspondant à la spécification mais avec des fragments vides. Ces fragments (généralement la

87 6.2. PRÉSENTATION SUCCINTE DE COQ

preuve logique que le terme correspond à la spécification) vont générer des buts qui devront être prouvés afin d’accepter le terme. Dans notre cas :

r e f i n e ( fun nmatch n r e t u r n n<>0 { q : n a t | ( S q )= n } with 0⇒fun h :(0 < > 0)⇒_

| ( S p ) fun h : ( ( S p) < >0) ( e x i s t _ p _ ) end ) .

_indique le fragment (trou) du terme à compléter avec des tactiques [218] et oùr e t u r n est une construc-tion syntaxique utilisée lors du filtrage d’un terme provenant d’un type dépendant. Avec lematch, nous distinguons les deux cas possibles du calcul et faisons ressortir la preuvehde la propriété de l’argument. Le retour est l’existence (avece x i s t) d’une valeurpqui vérifie la spécification. Nous avons alors ici deux buts à résoudre : 2 subgoal s n : n a t h : 0<>0 ============================ { q : n a t | S q = 0 } subgoal 2 i s : S p = S p

Le premier but se résout facilement par l’absurde :absurd (0 < > 0); auto . et le second est une trivialité. Nous pouvons maintenant afficher leλ-terme CCI complet de notre fonction :

P r i n t pred . pred = fun n : n a t match n as n0 r e t u r n ( n0<>0 { q : n a t | S q = n0 } ) with | O⇒ fun h:0 < >0 F a l s e _ r e c { q : n a t | S q = 0 } ( l e t H : = h i n ( l e t H0 : = fun H:0= 0 F al se⇒H ( r e f l _ e q u a l 0 ) i n fun H1:0 < >0⇒H0 H1 ) H) | S p⇒ fun _ : S p<>0 e x i s t ( fun q : n a t⇒S q = S p ) p ( r e f l _ e q u a l ( S p ) ) end : n : nat , n<>0 { q : n a t | S q = n }

Il est aussi possible de définir en Coq un terme par récurrence structurelle sur un objet inductif (une liste par exemple). Une définition récursive n’est acceptée que si tout appel récursif interne se fait sur un argument récursif qui est structurellement plus petit que l’argument récursif initial6. La préoccupation est, là encore, celle de la cohérence logique. Sans ces conditions sur les appels récursifs, il serait en effet aisé de construire des termes de n’importe quel typeA:

F i x p o i n t l o o p ( n : n a t ) : A : = l o o p n . D e f i n i t i o n i m p o s s i b l e : A : = l o o p 0 . Le système logique serait alors incohérent.

6.2.4 Coq et BSML

L’originalité du travail présenté dans ce chapitre est le «plongement» de BSML dans un système d’aide à la preuve. De précédents travaux visant à l’axiomatisation d’un langage BSP (voir travaux connexes au chapitre 12) ont montré qu’il était très délicat de tenir compte de tous ces aspects. Dans ce travail, le choix des langages utilisés, Coq et BSML, a été important car cela permet une axiomatisation simple et naturelle. Celle-ci pourra donc être employée pour la preuve de programmes BSP.

BSML apparaît comme un bon candidat pour une telle axiomatisation car il s’agit d’un langage pure-ment fonctionnel dont la sémantique est a priori assez bien adaptée à une description formelle. Coq est apparu comme un système d’aide à la preuve approprié à cette axiomatisation. C’est un système pour la programmation fonctionnelle et les types dépendants se sont avérés (pour l’instant) suffisamment puissants pour exprimer les propriétés formelles de nos fonctions BSML. Nous allons donc exprimer BSML dans ce calcul de types.

Documents relatifs