Table des matières

Introduction

  1. Contexte
  2. Fonctions Récursives
  3. Equation de point fixe
  4. Théorie des types
  5. Retour sur l'équation de point fixe

Utilisation de Coq

  1. Présentation
  2. Types de données
  3. Définition des fonctions
  4. Ordres Bien Fondés
  5. Construction d'ordres bien fondés: Ordres Lexicographiques

Fonctions sur les ordres bien fondés

  1. Expressions fonctionnelles d'ordre supérieur
  2. Étapes à suivre
  3. Exemple 1: wf_essai
  4. Exemple 2: Fonction d'Ackermann
  5. Définition par cas et types dépendants

Etude de cas

  1. Preuve de wf_essai
  2. Preuve de la fonction d'Ackermann
  3. comparaison des deux preuves

Traitement automatique

  1. Types de données: Représentation des expressions
  2. Types de données: Représentation des tactiques
  3. Génération des tactiques à partir des expressions
  4. Limitations

Conclusion

Bibliographie

Remerciements

Je tiens, avant tout, à remercier le Responsable scientifique Yves Bertot, pour m'avoir prise en stage. Je souhaite aussi lui témoigner ma reconnaissance pour l'aide et les conseils qu'il m'a apportés dans la réalisation de mon stage et la patience dont il a fait preuve. Sa connaissance et son soutien m'ont indiscutablement été d'un grand secours.

J'aimerais aussi adresser de chaleureux remerciements au Responsable permanent Laurence Rideau, qui m'a aussi servi de guide dans mes premiers pas dans le domaine de la recherche, ses conseils et son soutien moral m'ont beaucoup aidé dans ce travail et ont été d'un grand réconfort dans les moments de doute.

Ma vive gratitude au Professeur Michel Rueher, qui m'a donné la chance d'étudier en France et de faire mes études supérieures à l'École Supérieure en Sciences Informatiques. Ses conseils m'ont été très précieux.

Que soit particulièrement remercié mon Professeur Emmanuel Kounalis, qui m'a fait l'honneur d'être le rapporteur de mon travail.

Mes remerciments les plus vifs à Francis Montagnac, qui m'a beaucoup aidée lors de mes problèmes techniques liés à ma faible connaissance initiale du système.

Je remercie également Yann Coscoy pour sa patience et sa gentillesse, qui combinées à son expérience m'ont beaucoup aidée dans la compréhension du sujet de mon stage.

Lorsqu'une personne arrive dans un pays étranger elle est confrontée à une nouvelle culture, à un langage différent et à des personnes nouvelles. Je tiens donc, à remercier l'ensemble de l'équipe CROAP qui m'a accueilli à l'INRIA-Sophia Antipolis, pour son amitié, sa patience, ses conseils et son humeur. Grâce à cette équipe mon stage s'est déroulé dans une ambiance remarquable.

Introduction

Contexte

Depuis plusieurs années, le projet CROAP s'intéresse à l'utilisation de systèmes de preuve pour les tâches de programmation. Ces recherches sont menées suivant deux lignes directrices:

Dans les cas où les outils de preuve sont accompagnés d'outils de production de programmes à partir des algorithmes décrits, ceci permet de développer des programmes absolument sans erreur.

Des chercheurs de CROAP ont développé des outils et des compétences dans ces deux directions autour du Système Coq.

Dans toutes ces tâches, on est amené à construire des fonctions mathématiques récursives. C'est pourquoi nous présentons ici ce type de fonctions.

Fonctions Récursives

En mathématiques, la récurrence est le nom donné au procédé qui définit une fonction ou un processus à l'aide de la valeur de cette fonction en un autre point.

Un bon exemple d'une fonction définie récursivement est la fonction factorielle avec argument positif et entier :

          fact(0) = 1
          fact(n) = n * fact(n -1) si n 0

Ici fact(n)est définie à l'aide de fact(n -1), qui est définie à l'aide de fact(n -2), jusqu'à ce que l'on atteigne fact(0)qui est explicitement défini.

Toute définition récursive doit avoir une définition explicite pour une ou plusieurs valeurs des arguments, sinon la définition se refermerait sur elle-même.

En Coq, les fonctions peuvent être définies récursivement en respectant des contraintes: par récurrence primitive sur la structure des arguments ou par référence à un ordre bien fondé; c'est-à-dire que la fonction ne se rappelle récursivement que sur des valeurs décroissantes ordonnées par un ordre bien fondé (un ordre ne contenant pas de chaînes infinies décroissantes), assurant que la fonction ne pourra pas se rappeler indéfiniment.

Equation de point fixe

Naturellement quand on parle de fonction récursive f, il existe une équation de point fixe:

                     f x = F (f, x)

F est une fonctionnelle, c'est à dire une fonction dont l'un des arguments est une fonction. Par exemple, la fonction Factorielle est le point fixe d'une fonction F qui est définie par:

         F(f , a) = if a = 0 then 1
                             else a*f(a-1)

Le but final du stage est de trouver une méthode automatique qui aboutira aux preuves de l'équation de point fixe, pour n'importe quelle fonction définie sur un ordre bien fondé. Pour expliquer en détails l'équation de point fixe, il est indispensable de décrire la théorie des types.

Théorie des types

Aujourd'hui, on désigne généralement par " Théorie des types " un formalisme logique dont les objets sont des λ-termes typés. Il existe plusieurs formalismes rentrant dans cette catégorie, et ils se distinguent essentiellement par le système de types plus ou moins riche des objets manipulés, ainsi que par la logique pour parler de ces objets. On peut citer en particulier la logique d'ordre supérieur de Church, la Théorie des Types de Martin-Löf et le Calcul des Constructions Inductives.

Les objets de la logique d'ordre supérieur de Church sont les λ-termes simplement typés. Les deux autres formalismes utilisent essentiellement des variantes du langage de programmation ML.

Ils se distingent par la logique utilisée:

Un point commun essentiel au Calcul des Constructions et à la théorie de Martin-Löf est bien sûr qu'ils sont construits sur l'isomorphisme de Curry-Howard. Rappelons que cela signifie que:

Par exemple: Pour prouver la propriété de la symétrie de Plus, on a la preuve suivante:

plus_sym=
      [n, m:nat]
      nat_ind [n0:nat] (plus n0 m)= (plus m n0) (plus_n_0 m)
      [y:nat]
      (H:(plus y m)=(plus m y)]
(eq_ind nat (S (plus m y)) [n0:nat] (S (plus y m))= n0

(eq_S (plus y m) (plus m y) H) (plus m (S y)) (plus_n_Sm m y)) n)
: (n,m:nat) (plus n m)=(plus m n)

En simplifiant, on peut dire que les avantages informatiques de cette approche sont :

Retour sur l'Equation de point fixe

L'écriture de la fonctionnelle F de l'équation de point fixe, est obtenue facilement quand on est dans le cas d'une fonction structurellement récursive. Par exemple, nous avons donné sa valeur pour la fonction Factorielle.

Par contre, l'écriture de F devient plus compliquée quand on se retrouve avec des fonctions définies sur un ordre bien fondé.

Dans le cas de récursion sur un ordre bien fondé, on impose à F de prendre en premier argument la donnée x de type A,(A étant un ensemble prédéfini) et en deuxième argument une fonction f qui ne pourra être appelée que sur des arguments plus petits que x.

Ceci peut être exprimé en imposant à f le type suivant :

                          f:(y:A)(y < x) -> B

Ce type exprime que f prend deux arguments, le premier argument y de type A et le deuxième étant une preuve que y est plus petit que x.On voit que le type de f dépend de x et que le type du deuxième argument de f dépend de y. Cette caractéristique est permise par les types dépendants du système Coq. C'est pour cette raison qu'on a été obligé d'intervertir les arguments de F.

F doit donc avoir le type suivant: F:(x:A)(f:(y:A)(R y x)-> B)B.

Le système Coq fournit un opérateur appelé well_founded_induction qui prend une preuve que l'ordre R est bien fondé, une fonctionnelle F et retourne une fonction de A dans B.

Le type de well_founded_induction est en fait plus compliqué que nous le laisse entendre cette explication intuitive. Ce type utilise la propriété well_founded et est donné formellement par :

    : (A:Set)
       (R:A->A->Prop)
       (well_founded A R)
        ->(P:A->Set) ((x:A) ((y:A) (R y x) -> (P y)) -> (P x)) -> (a:A) (P a).

Avec le théorème well_founded_induction, l'équation de point fixe devrait avoir la forme suivante:

well_founded_induction F a = F a [y:A] [h:y<x](well_founded_induction F)

La fonction retournée est le point fixe de F. Mais la démonstration de cette équation est difficile à obtenir et le sujet de ce stage est de fournir une approche systématique à ce problème. Nous avons utilisé le système Coq à cet effet. Pour cela, nous allons présenter dans la partie suivante ce système avant de décrire son utilisation dans notre travail.

Utilisation de Coq

Présentation

Coq est un système d'aide à la preuve développé à l'INRIA-Rocquencourt et à l'Ecole Normale Supérieure de Lyon et depuis 1997 au L.R.I. à Orsay.

Ce système est écrit en Ocaml. Le langage Ocaml est un langage fonctionnel de la famille ML développé à l'INRIA-Rocquencourt.

  1. Ce système s'applique à la logique d'ordre supérieur.
  2. C'est un vérificateur de types (pour le calcul des constructions inductives).
  3. Il contient un environnement courant, qui contient les définitions d'objets, les théorèmes et leurs preuves, les variables abstraites et les axiomes.
  4. Pour prouver une proposition P, on tape "Lemma gogo: P " ou "Theorem gogo: P ".
  5. On construit alors la preuve à l'aide d'instructions spécialisées qui s'appellent "des tactiques".
  6. La quantification universelle est notée par (a:A) et se lit " pour tout a dans A".

    Les crochets définissent les fonctions (comme les lambda-abstractions du λ-calcul).

    Par exemple :

    [a:A]t est la fonction qui à a de type A associe le terme t.

    La quantification universelle est le pendant dans les types de la définition des fonctions:

    Si t est de type B, alors [a:A]t est de type (a:A)B.

    Coq abrège (a:A)B en A->B quand B ne dépend pas de la variable a, et on retrouve le type des fonctions de A dans B.

  7. Prop et Set sont deux constantes spéciales du système; ce sont les types des propositions et des types de données qu'on développera plus tard.

Types de données récursifs et schéma de récurrence

Habituellement, un ensemble défini inductivement est le plus petit ensemble vérifiant un certain nombre d'axiomes. Par exemple l'ensemble des entiers naturels est le plus petit type clos par ses deux constructeurs qui sont O et S (Successeur).

Coq permet ce genre de définition, pour cela on dispose du mot clef Inductive. On définit donc les entiers par:

Inductive nat : Set :=
            O : nat
          | S : nat -> nat.

Ce qui construit effectivement les objets nat:Set, 0:nat, S:nat -> nat, mais le système Coq fournit de plus un théorème de récurrence qui a la forme suivante:

Etant donné un P qui est un prédicat sur les entiers; pour que la proposition P(n) soit vraie pour tout n; alors on a besoin de prouver que les deux conditions suivantes sont vraies:

  1. P(0) est vrai
  2. Pour tout n, si P(n) est vraie alors P(S(n)) est vraie aussi .

En syntaxe Coq, c'est à dire dans une logique d'ordre supérieur, ce schéma sera exprimé comme une proposition:

(P: nat -> Prop)(P 0) -> ((m: nat) (P m) -> (P (S m))) -> (n: nat) (P n)

De fait, la définition d'un type inductif génère automatiquement une preuve du schéma de récurrence correspondant. Si le type inductif est nommé I, la preuve du schéma de récurrence s'appellera I_ind.

Par exemple:

Coq < Check nat_ind.
nat_ind
    : (P: nat -> Prop) (P 0) -> ((n: nat)(P n) -> (P (S n))) -> (n: nat) (P n).

Défintion des fonctions

Dans Coq il y a trois façons différentes de définir des fonctions:

λ-calcul typé:

  1. Correspond à la définition de la fonction sans récursion.

    Le λ-calcul, est la formalisation de la notation mathémathique usuelle pour définir des fonctions.

    Par exemple:     f: nat -> nat
                         x  -> x+2

    devient en utilisant la syntaxe de Coq:

    Coq <  Definition  f := [x:nat] (S(S x)).

    On peut alors calculer:

    Coq < Compute (f (S O)).
          = (S(S(S O)))
          : nat.

    On parle de λ-calcul, car en logique Mathémathique, on note en général λx: T.t

    Pour ce qui s'écrit [x:T]t en Coq.

Fonctions Récursives sur les Types Inductifs

  1. En plus du λ-calcul typé, on peut, définir en Coq, des fonctions par cas, sur les types inductifs. Par exemple, la fonction prédecesseur peut être définie grâce à la commande suivante:

    Coq < Pred = [n:nat] <nat> Cases n of
                                     O -> O
                                |(S u) -> u
                                end.
             : nat -> nat.

    En mathématique usuelle, une fonction peut être définie par son graphe.

    En Coq, une fonction est définie par un algorithme ou une méthode de calcul. Les fonctions de Coq sont donc à mi-chemin entre les programmes et les fonctions (ou plutôt les applications ).

    On peut utiliser le même mécanisme pour définir des fonctions récursives.

    Supposons par exemple, qu'on veut définir l'addition:

    Sa définition en ML est :

    let rec plus n m =
                match  n with
                       O -> m
                  |(S p) -> S (plus p m);;

    La même fonction est définie en Coq en utilisant la construction "Fixpoint" qui permet de définir des fonctions récursives.:

    Fixpoint plus [n: nat] : nat-> nat :=
                  [m: nat]  Cases n of
                                  O -> m
                              |(S p)-> (S ( plus p m))
                             end.

    Cette fonction plus est une fonction structurellement récursive par rapport à son premier argument n. La fonction plus se rappelle sur l'argument p qui est un sous terme du (S p) lors de son appel récursif. Toute fonction structurellement récursive est acceptée par le système Coq.

    L'exemple suivant ne correspond à aucune fonction mathématique, car son algorithme ne termine pas. Il est refusé par le système :

    Coq < Fixpoint foo [n: nat] : nat :=
                       Cases n of
                             O -> O
                        |(S a) -> (foo ( S a))
                       end.

    Cette condition de terminaison est nécessaire pour assurer la cohérence logique du système.

    Avec la définition par Fixpoint on aura une vérification automatique de type et de la condition d'arrêt de la fonction car Fixpoint permet seulement la récursion sur des sous termes structurels. De plus, l'évaluation de la fonction est bien intégrée au système.

    Avec cette définition on est capable de calculer automatiquement que :

                   plus(O,O) = O.

Définition de la fonction par un Ordre Bien Fondé

Ce troisième choix de définition de fonction est le sujet de la troisième partie de ce rapport.

Ordres Bien Fondés

Définition d'un Ordre Bien Fondé:

Un Ordre Bien Fondé est un ordre défini sur un ensemble tel qu'il n'existe pas de chaîne infinie décroissante.

Avec un tel ordre, on peut raisonner en utilisant un principe d'induction bien fondée qui a la forme suivante:

Soit < un ordre Bien Fondé sur un ensemble A.
Soit P une propriété sur les éléments de l'ensemble A.
On a alors:aA P(a)
Si et seulement si on a
aA.((bA. b<a ->P(b))->P(a)).

Signalons qu'avec une induction bien fondée, il s'agit d'une méthode de preuve par induction sur une relation bien fondée, qui généralise la récurrence sur les entiers et l'induction structurale sur les termes.

Un théorème important pour les ordres bien fondés est le suivant:

Un ordre R est un ordre bien fondé sur un ensemble A

Si et Seulement Si tout sous ensemble non vide Q de A a un élément minimal.

Avec l'Ordre Bien Fondé on peut prouver la terminaison d'une fonction.

Pour montrer qu'une fonction termine, on peut montrer que durant son execution elle fait décroître une valeur dans un ensemble bien fondé c'est à dire défini par un Ordre bien fondé et donc est appelée sur une suite décroissante non infinie par définition.

En Coq, la manipulation des fonctions définies par récursion primitive est particulièrement bien intégrée dans le moteur de raisonnement. En revanche, les fonctions définies par récurrence sur un ordre bien fondé sont plus difficiles à mettre en oeuvre.

Pour donner un exemple, une fonction "f" décrite par récurrence bien fondée de A vers B est décrite par une fonctionnelle F de type (A ->B) ->(A ->B) telle que f(x)=F(f,x).

Ce qui est dommage en Coq, c'est que cette égalité n'est pas fournie et difficilement prouvable.

Pourquoi l'Ordre Bien Fondé:

On a choisi l'ordre bien fondé car il nous permet de vérifier si la fonction se rappelle sur un argument plus petit en utilisant des ordres plus généraux que l'ordre structurel. Par exemple, la fonction d'Ackermann ne se rappelle pas sur un argument structurellement plus petit mais sur un argument plus petit pour l'ordre lexicographique sur les paires d'entiers. Nous rappelons que la fonction d'Ackermann est définie en ML par :

let rec  ack = function
              (0, m) -> S(m)
        | (S(n), 0)  -> ack(n, (S 0))
      | (S(n), S(m)) -> ack(n, ack(S(n), m));;

Construction d'ordres bien fondés: Ordres lexicographiques

Parmi les ordres bien fondés utiles pour prouver certaines propriétés de terminaison, il faut mentionner les ordres définis lexicographiquement sur un produit cartésien. Étant donnés deux ordres < 1 et > 2 sur deux ensembles E 1 et E 2 , l'ordre lexicographique < sur E 1 × E 2 défini à partir des ordres < 1 et < 2 est défini par:

(x, y) < (x', y') Si et Seulement Si x < 1 x' ou (x = x' et y < 2 y')

Il est assez facile de vérifier que si les ordres < 1 et > 2 sont bien fondés, l'ordre < l'est également.

On peut par exemple utiliser l'ordre lexicographique sur les couples d'entiers naturels pour prouver la terminaison de la fonction d'Ackermann .

Le principe de preuve est le suivant :

Etant donnée une relation d'ordre bien fondé < sur un ensemble E, pour démontrer une propriété

x P(x), il suffit de montrer (y < x P(y)) => P(x).

Par conséquent, pour démontrer que le calcul de ack(m,n) termine pour tout couple (m,n) d'entiers naturels, il suffit de montrer ce fait sous l'hypothèse que ack(m',n') termine pour tout couple (m', n') plus petit que (m,n). Dans le cas où m et n sont différents de 0, on peut ainsi utiliser le fait que (m,n-1) est plus petit que (m,n) pour conclure que le calcul de ack(m,n-1) se termine. Si on appelle alors p la valeur de cette expression , on peut de nouveau utiliser le fait que le couple (m-1,p) est plus petit que (m,n) pour conclure que le calcul de ack(m-1,p) se termine et que par conséquent celui de ack(m,n) se termine aussi. On a donc établi que la fonction ack est totale (sur les couples entiers naturels).

Fonctions sur les ordres bien fondés

L'intérêt de cette partie est de présenter ce qu'est la fonctionnelle associée à une définition récursive, de donner les différentes étapes à suivre pour définir une fonction basée sur un ordre bien fondé, de prouver l'équation de point fixe et de présenter deux exemples de preuves.

Expressions fonctionnelles d'ordre supérieur

Les fonctions étant considérées comme des valeurs à part entière, il est naturel qu'elles puissent apparaître comme arguments ou résultats d'autres fonctions.

Les fonctions prenant des arguments fonctionnels ou rendant des résultats fonctionnels sont dites d'ordre supérieur [G.Cousineau].

Considérons par exemple la fonctionnelle h qui à toute fonction f(x) associe la fonction f(x)/x.

On peut la définir en ML par:

let h= (fun f -> (fun x -> f(x)/x));;
    h:(float -> float) -> float -> float = <fun>.

Etapes à suivre

Pour définir une fonction f basée sur un ordre bien fondé et prouver l'équation de point fixe, on a besoin de:

  1. Définir l'ensemble de départ.
  2. Donner l'ordre sur laquelle va être définie notre fonction.
  3. Prouver que cet ordre choisi est bien un ordre bien fondé.
  4. Définir l'ensemble d'arrivée.
  5. Prouver que cet ordre bien fondé vérifie la propriété de transitivité, car elle est indispensable pour la suite de la preuve.
  6. Définir la fonctionnelle F correspondante.

Premier exemple: wf_essai

Soient A:Set un ensemble de départ, n une fonction de A vers A et B:Set l'ensemble d'arrivée. Dans notre premier essai, nous avons supposé que R est un ordre bien fondé sur l'ensemble A et que H_n_decrease est une preuve que (n a) > R a selon l'ordre R ainsi choisi.

Puis nous avons considéré une fonction f qui traite deux cas qui sont définis à l'aide de la variable base_dec:

            base_dec: (a: A) {(base a)}+{~(base a)}

Qui se lit pour tout x élément de A, soit x est le cas de base, soit x n'est pas le cas de base.

Definition F:=
       [a:A] [f:(y:A) (R y a) -> B]
        <B>  Case (base_dec a) of
                [h: (base a)] (gb a)
                [h:~(base a)] (gr (f (n a) (H_n_decrease a h)))
             end.

Signalons que le cas de base est le cas d'arrêt de la fonction où il n'apparaît aucun appel récursif et qui étant donné un élément de type A retourne, à l'aide de la fonction gb,un élément de type B. Par contre, l'autre cas est le cas de la récursivité, on a donc besoin d'une preuve supplémentaire assurant que lors de l'appel récursif, on se rappelle sur un argument plus petit suivant l'ordre choisi.

Dans notre premier exemple, l'argument sur lequel on se rappelle est (n a) et H_n_decrease est la preuve que (n a) est plus petit que a. La deuxième argument "h" est donnée pour préciser que l'on est dans le cas de ~(base a).

            H_n_decrease:(a:A) ~(base a) -> (R (n a) a)

Durant la preuve de l'équation de point fixe, on a besoin du fait que l'ordre ainsi choisi vérifie la propriété de transivité; Ici on a donné la propriété de transivité à R comme un axiome:

Axiom R_trans: (a, b, c:A) (R a b)->(R b c)->(R a c).

Deuxième exemple: Ackermann

Après avoir prouvé l'équation de point fixe dans notre premier exemple, on a choisi la fonction d'Ackermann comme un deuxième exemple pour notre preuve, car c'est une fonction non primitive récursive. On a suivi les 6 étapes suivants:

  1. L'ensemble du départ est A:Set qui est l'ensemble des couples : nat*nat.
  2. L'ordre choisi est l'ordre lexicographique lex.
  3. La preuve que lex est un ordre bien fondé est basée sur le fait que l'ordre < est aussi un ordre bien fondé.
  4. L'ensemble d'arrivée est nat.
  5. La preuve que lex vérifie bien la transivité est donnée par lex_trans.
  6. La fonctionnelle F a la forme suivante:

Definition ackermann :=
(well_founded_induction nat*nat lex well_founded_lex [_:nat*nat]nat F).
Définition F:(a:nat * nat) ((b:nat * nat) (lex b a) -> nat) ->nat :=
                      [a:nat * nat]
<[a:nat * nat] (f:(b:nat * nat) (lex b a) ->nat)nat > Cases a of
        (pair O n) => [f:(b:nat * nat) (lex b (O,n)) ->nat] (S n)
  | (pair (S n) O) =>
                      [f:(b:nat * nat) (lex b ((S n),O)) -> nat]
                       (f (n,(S O)) (th2 n O (S O)))
|(pair (S n) (S m))=>
                     [f:(b:nat * nat) (lex b ((S n),(S m))) -> nat]
                       (f
                        (n,(f ((S n), m) (th3 (S n) m)))
                        (th2 n (S m) (f ((S n),m) (th3 (S n) m)))
         end.

Définition par cas et types dépendants

Pour définir la fonctionnelle F d'Ackermann, on a besoin des théorèmes suivants:

  1. Théorème th2:
    Theorem th2: (n, m, p:nat) (lex (n, p) ((S n), p)).

    C'est dans le cas où le premier argument du premier couple est inférieur au premier argument du deuxième couple.

  2. Théorème th3:
    Theorem th3: (n, m:nat) (lex (n, m) (n, (S m))).

    C'est dans le cas où les deux premiers arguments des deux couples sont égaux alors il faut passer au deux autres arguments pour les comparer.

Les problèmes rencontrés lors de la définition de la fonctionnelle F d'Ackermann sont les suivants:

Quand on utilise la définition par cas normalement toutes les parties droites (à droite de =>) des règles doivent être de même type.

Mais dans notre définition le Type Checker de Coq n'arrive pas à unifier ((S n) , 0) qui se trouve dans la partie droite du deuxième cas avec la paire a .

On a donc été obligé d'ajouter

< [a: nat * nat ].(f:(b: nat *nat)(lex b a)-> nat) nat >

qui n'est autre que le type de retour de Cases a,pour qu'on puisse changer le type de a (parties gauches) à chaque cas. (On parle d'un type dépendant de la forme de a).

Donc pour un certain couple donné on aura une fonction f qui prend pour argument un deuxième couple qui vérifie bien l'ordre lexicographique et son type de retour est bien un nat.

Et on aura comme réponse :

  1. Au premier cas : (S n).
  2. Au deuxième cas : la fonction f appliquée au couple (n ,(S 0))et une preuve que (n ,(S 0)) est plus petit que ((S n),0) pour l'ordre > lex grâce au théorème th2.
  3. Au dernier cas: On a la fonction f appliquée au couple (n ,(f((S n ),m) et une preuve que ((S n),m) > lex ((S n),(S m)) en utilisant le théorème th3; et une preuve que le couple(n,(f((S n),m))) > lex ((S n),(S m))en utilisant le théorème th2.

Donc on a permis au système Coq de bien vérifier dans chaque cas que le deuxième couple de (lex b ...) est bien le a dont on parle.

Dans chaque cas, f a besoin d'une preuve et d'un énoncé qui sont differents. Alors on peut dire que le type de f dépend de la valeur de a qui change à chaque cas.

Etude des cas

Dans cette partie, nous présentons les scripts de preuve des deux exemples: wf_essai et Ackermann et nous terminons par une comparaison de ces deux exemples.

  1. Preuve de wf_essai
    Lemma l1:
      (a:A)
      (well_founded_induction A R H_well_founded_R P F a) =
      (F a [y:A] [h: (R y a)] 
                 (well_founded_induction A R H_well_founded_R P F y)).
    Intro.
    Apply (well_founded_ind
             A R H_well_founded_R
             [a: A]
             (well_founded_induction A R H_well_founded_R P F a) =
             (F
              a
              [y: A] [_: (R y a)] 
                     (well_founded_induction A R H_well_founded_R P F y)).
    Intro x.
    Unfold 3 well_founded_induction.
    Elim (H_well_founded_R x) using Acc_ind2.
    Simpl.
    Intros x0 accessible H_rec_acc H_rec_well_founded.
    Cut (y:A) (y0: (R y x0))
        <(P y)>
           (Acc_rec
               A R P
               [x:A] [_:(y1:A) (R y1 x) -> (Acc A R y1)]
                       [H2:(y1:A) (R y1 x) -> (P y1)]
               (F x H2) y (accessible y y0)) =
               (well_founded_induction A R H_well_founded_R P F y).
    Intro H_rec_acc'.
    (* La partie qui suit le Unfold F est specifique à chaque fonction traitée:*)
    (***************************************************************************)
    Unfold F.
    Case (base_dec x0); Auto.
    Intro.
    Rewrite H_rec_acc';Auto.
    Intros y1 H';Try Assumption.
    Rewrite H_rec_well_founded.
    Apply H_rec_acc.
    Intros.
    
    Apply H_rec_well_founded.
    Apply R_trans with y:=y1; Auto.
    Exact H'
    Qed.
  2. Preuve de la fonction d'Ackermann
    Lemma l1:
      (a:nat*nat)
      (well_founded_induction 
                   nat*nat lex H_well_founded_lex [_:nat*nat]nat F a) =
      (F 
         a
        [b:nat*nat] [h: (lex b a)] 
        (well_founded_induction
                     nat*nat lex H_well_founded_lex [_:nat*nat]nat F b)). 
    Intro.
    Apply (well_founded_ind
             nat*nat lex H_well_founded_lex
             [a:nat*nat]
             (well_founded_induction
                          nat*nat lex H_well_founded_lex [_:nat*nat]nat F a)=
             (F
                a
                [y:nat*nat] [_: (lex y a)] 
                (well_founded_induction 
                         nat*nat lex H_well_founded_lex [_:nat*nat]nat F y))).
    Intro x.
    Unfold 3 well_founded_induction.
    Elim (H_well_founded_lex x) using Acc_ind2.
    Simpl.
    Intros x0 accessible H_rec_acc H_rec_well_founded.
    Cut (y:nat*nat) (h: (lex y x0))
           <nat>
           (Acc_rec
               nat*nat lex [_:nat*nat]nat
               [x:nat*nat] 
               [_:(y0:nat*nat) (lex y0 x) -> (Acc nat*nat lex y0)]
               [H2:(y0:nat*nat)(lex y0 x) ->  nat]
               (F x H2) y (accessible y h)) =
           (F
            y
            [y1:nat*nat] [_:(lex y1 y)] 
            (well_founded_induction 
                       nat*nat lex H_well_founded_lex [_:nat*nat]nat F y1))).
    Intro H_rec_acc'.
    2:Intros y y0.
    2:Apply H_rec_acc.
    2:Intros y1 H'.
    2:Apply H_rec_well_founded.
    2:Apply lex_trans with b:=y; Auto.
    Generalize H_rec_well_founded H_rec_acc' accessible.
          

    Cette partie qui suit le Unfold F est specifique à cette fonction

    Unfold  7 9 F.
    Case x0. Intro n; Case n. Intros n0 a1 H' H'0;Auto. Intros p n0; Case n0. Clear H_rec_acc' H_rec_well_founded H_rec_acc accessible. Intros accessible H_rec_well_founded H_rec_acc'. Rewrite H_rec_acc'. Rewrite H_rec_well_founded; Auto. Apply th2. Clear H_rec_acc' H_rec_well_founded H_rec_acc accessible. Repeat Rewrite H_rec_acc'. Repeat Rewrite H_rec_well_founded; Auto. Apply th3. Apply th2. Apply th3. Qed.
  3. Comparaison des deux preuves

    En comparant les deux preuves wf_essai et Ackermann, on a remarqué qu'elles étaient comparables jusqu'à l'étape où l'on s'interresse à la structure interne de la fonction traitée. Dans le cas du wf_essai, juste après avoir remplacé la fonctionnelle F par sa définition (en utilisant la tactique Unfold F), on execute la tactique Case x0 et on traite les différents cas; alors que dans le cas d'Ackermann on a remarqué que, lors de notre preuve, on a déjà fixé x0 dans les hypothèses alors que le but courant est de faire une preuve pour tout x0,c'est à dire x0 désigne n'importe quel objet. D'où si on execute Case x0 directement après le Unfold F, comme dans le cas de wf_essai, le système nous génère une erreur car on a toujours des types dépendants sur les arguments. Pour résoudre ce problème, on a introduit la tactique Generalize sur toutes les hypothèses qui contiennent x0 qui sont: accessible, H_rec_well_founded et H_rec_acc' avant la tactique Unfold F.

    1. accessible:y:A. (R y x0) => (Acc A R y)
    2. H_rec_well_founded:y:A.(R y x0) => 
      <P y>
      (well_founded_induction A R H_well_founded_R P F y) =
        (F
          y
          λy0:A.λ_:(R y0 y).

      (well_founded_induction A R H_well_founded_R P F y0))

    3. H_rec_acc':y:A.y0:(R y x0) 
      <P y>
      (Acc_rec
          A R P
          λx:A
          λ_:y1:A.(R y1 x) => (Acc A R y1).
         λH2:y1:A.(R y1 x) => (P y1 x) =>(P y1).(F x H2) y (accessible y y0)) =
      (F
      y
      λy1:A.λ_:(R y1 y).

      (well_founded_induction A R H_well_founded_R P F y1))

    Signalons que H_rec_well_founded et H_rec_acc' sont deux hypothèses de récurrences. La première est basée sur le fait que l'ordre qu'on a choisi est un ordre bien fondé, d'où tout élément est accessible. La deuxième, nous sommes obligés d'ajouter une preuve (accessible y y0) qui est la preuve que y est accessible car y est plus petit d'un certain x0 qui lui, est accessible.

    Un élément x est dit accessible si et seulement si tout élément plus petit que x est accessible (Il s'agit d'une définition récursive). Ainsi un ordre est bien fondé si et seulement si tout élément est accessible. En Coq, le théorème de well_founded est basé sur le fait que tout élément est accessible.

    Une fois qu'on applique le Unfold F la partie qui suit dans notre preuve dépend de la structure de la fonction traitée. Il suffit alors de regarder la structure de la fonction et appliquer les tactiques correspondantes.

    C'est pour cette raison qu'il faut généraliser notre preuve et faire une méthode automatique qui aboutira aux tactiques de preuves qu'il faut pour prouver l'équation de point fixe, pour n'importe quelle fonction définie sur un ordre bien fondé.

Traitement automatique

Types de données: Représentation des expressions

La première étape dans notre traitement était de préciser les différentes formes que peut prendre une expression. Une expression peut être:

1-QuestioM : C'est le système qui va essayer de déterminer le lambda terme

manquant par unification.

2- Ident : Un identificateur du type chaîne.
3- App : C'est une application d'une fonction sur une liste d'expressions.
4- Cases : Permet la définition d'une fonction par cas.
5- Lambda : Permet la définition d'une fonction.
6- Forall : C'est le type de la fonction.

Types de données: Représentation des tactiques

Les tactiques sont des règles de transformations logiques qui sont valides. Ceci assure la correction des preuves qui les utilisent. Néanmoins, toute preuve obtenue par une succession de tactiques est, avant d'être acceptée, formellement vérifiée par le mécanisme de typage de Coq: le type d'une preuve doit être le théorème qu'elle est censée démontrer.

Dans les exemples donnés dans la suite du rapport, les hypothèses sont au-dessus de la barre et le but à prouver en-dessous. Pour prouver le but, on dispose de "tactiques", qui transforment le but et les hypothèses. L'idée étant de simplifier le but jusqu'à le rendre trivial.

Génération des tactiques à partir des expressions

Une fois qu'on a terminé nos deux preuves, le but suivant est d'essayer de généraliser notre preuve pour qu'elle s'applique sur n'importe quelle fonction f définie sur un ordre bien fondé.

Pour cela, nous avons utilisé le langage ML. Nous avons introduit des types de données: représentation des expressions et représentation des tactiques.

"fexp1" est le nom de la fonction qui traite la partie commune à toutes les fonctions. Elle donne une séquence de tactiques à appliquer dans notre preuve jusque là où on arrive à la structure de la fonction récursive proprement dite donc on applique la fonction "fexp".

"fexp" est le nom de la fonction récursive définie par cas sur cette structure de données (expression); elle prend en premier argument un string option qui distingue deux cas:

  1. None: qui correspond au début de notre recherche dans la fonctionnelle F où on ne connait pas encore le nom de la fonction f qui sera appelée lors des appels récursifs.
  2. Some(f) dans le cas contraire.

Comme deuxième argument fexp prend le corps de la fonctionnelle F et selon la forme de l'expression qui se trouve dans la fonctionnelle elle renvoie la tactique correspondant.

Par exemple, dans le cas où, la fonction contient un Cases a alors la tactique correspondante, si f n'apparait pas dans a, est de faire une séquence de tactiques:

Sequence[Case(a); Brackets(map (frule fname) rules)]

On obtient autant de buts qu'on a de cas dans Case(a) et on applique frule sur chacune de ces règles.

Limitations

Dans notre étude, nous avons dû nous limiter au cas où notre fonctionnelle F est une Lambda abstraction, nous ne traitons pas les autres cas.

En ce qui concerne le contenu de la fonctionnelle, nous avons aussi quelques restrictions:

  1. Dans le cas de Cases(t, a, rules), on se limite au cas où f n'apparait pas dans a.
  2. Pour les patterns, on se limite au cas d'un pattern primitif et on a 2 cas:
    1. Pat_ident qui est un constructeur et dans ce cas on ne fait pas des Intros.
    2. Pat_app est un constructeur appliqué à une liste d'identificateurs. La fonction d'Ackermann ne marchera pas dans ce cas là. Alors on a dû casser à la main le pattern et construire des Cases imbriqués.

    On se restreint au pattern primitif, car sinon, à chaque fois qu'on a un pattern, il faut vérifier si c'est un constructeur ou non et pour cela il faut avoir un tableau qui contient la liste de tous les constructeurs de tous les types récursifs connus dans le texte.

  3. Dans le cas de App(expression,[liste d'expressions]), la partie gauche de App doit être un identificateur et les autres types d'expression ne sont pas traités.
  4. Dans le cas d'application de Cases a, il faut conserver l'ordre des constructeurs des types inductifs donnés lors de la définition.

Conclusion

En Coq, la manipulation des fonctions définies par récursion primitive est particulièrement bien intégrée dans le moteur de raisonnement. En revanche, les fonctions définies par récurrence sur un ordre bien fondé sont plus difficiles à mettre en oeuvre. En effet, en Coq la preuve de l'équation de point fixe de fonctions basées sur un ordre bien fondé n'était pas réalisée automatiquement.

L'objectif de mon stage est d'intégrer une méthode automatique qui aboutira aux tactiques de preuve qu'il faut pour prouver l'équation de point fixe, pour n'importe quelle fonction définie sur un ordre bien fondé.

Dans cet objectif, nous avons tout d'abord étudié deux preuves sur des cas particuliers et ensuite nous avons comparé leur scripts de preuve. Ceci nous a permis de généraliser une preuve et de définir une méthode automatique.

Comme nous avons cité dans le paragraphe précédent " Limitations ", certaines limitations ont été imposées dans notre méthode. Nous planifions de résoudre ces problèmes dans un travail ultérieur.

Une des perspectives directes de ce travail est de générer une nouvelle hypothèse de récurrence qui unit les deux hypothèses de récurrence H_rec_well_founded et H_rec_acc', qui sont nécessaires dans notre preuve.

Bibliographie

  1. [R.Péter] Rózsa Péter. Recursive Functions In Computer Theory. Halested Press,1981.
  2. [GRÉGOIRE] Informatique-Programmation Tome 2: La spécification récursive et l'analyse des algorithmes. MASSON, 1988.
  3. [G.Cousineau] Guy Cousineau¸Michel Mauny. Approche fonctionnelle de la programmation. Ediscience international, 1995.
  4. [Lalement90] R.Lalement. Logique, Réduction, Résolution. Masson, 1990.
  5. [B.Werner] Benjamin Werner, Jean-Christophe Filliâtre. The Coq Proof Assistant Execution of extracted programs in Caml and Gofer Version 6.1. INRIA, 1996.
  6. [G.Huet] Gérard Huet, Gilles Kahn, Christine Paulin-Mohring. The Coq Proof Assistant : A Tutorial : Version 6.1. INRIA, 1996.
  7. [P.Weis] Pierre Weis, Xavier Leroy. Le langage Caml. InterEditions, 1993.
  8. [T.Coquand] Thierry Coquand. Courcelle(ed.). An introduction to type theory in Logique et informatique: une introduction. INRIA, 1991.
  9. [DOWEK98] Gilles Dowek. Théories des types. [http://pauillac.inria.fr/~dowek/theories_des_types.ps.gz]
  10. [C.Paulin] Christine Paulin-Mohring. Courcelle(ed.). Réalisabilité et extraction de programmes in Logique et informatique: une introduction. INRIA, 1991.
  11. [T.Coquand] Thierry Coquand, Gérard Huet. Concepts Mathématiques et Informatiques Formalisés Dans Le Calcul Des Constructions. INRIA,1985.
  12. [C.Paulin] Christine Paulin-Mohring. Marc Bezem, Jan Friso Groote(eds.). Inductive Definitions in the system Coq Rules and properties in Typed Lambda Calculi and Applications. Springer-Verlag, 1993.
  13. [B.Jacobs] Bart Jacobs and Tom Melham. Marc Bezem, Jan Friso Groote(eds.). Translating Dependent Type Theory into Higher Order Logic in Typed Lambda Calculi and Applications. Springer-Verlag, 1993.