• Aucun résultat trouvé

Depuis l'apparition des premiers ordinateurs, chaque génération de programmeurs à du faire face au problème de la {abilité du code. Les langages statiquement typés tels que Java, Haskell, OCaml, Rust ou Scala ont attaqué ce problème avec des véri{cations statiques, au moment de la compilation, pour détecter des programmes incorrects. Leur typage fort est particulièrement utile quand plusieurs objets incompatibles doivent être manipulés au même moment. Par exemple, un programme qui calcule une addition sur un booléen (ou une fonction) est immédiatement rejeté. Durant les dernières années, les avantages du typage statique ont même été reconnus au sein de la communauté des langages dynami-quement typés. Des systèmes de véri{cation statique du typage sont dorénavant disponibles pour Javascript [Microso{t 2012, Facebook 2014] ou Python [Lehtosalo 2014].

Dans les trente dernières années, des progrès signi{catifs ont été fait dans l'application de la théorie des types aux langages de programmation. La correspondance de

Curry-Howard, qui lie les systèmes de types des langages de programmation fonctionnels à la logique mathématique, a été explorée dans deux directions principales. D'un côté, les assis-tants à la preuve comme Coq ou Agda sont basés sur des logiques très expressives [Coquand

1988, Martin-Löf 1982]. Pour montrer leur cohérence logique, les langages de programmation

sous-jacents doivent être restreints aux programmes qui peuvent être montrés terminant. Ils interdisent donc les formes de récursion les plus générales. De l'autre côté, les langages de programmation fonctionnelle comme OCaml ou Haskell sont adaptés à la programma-tion, car ils n'imposent pas de restriction sur la récursion. Cependant, ils sont basés sur des logiques qui ne sont pas cohérentes, ce qui implique qu'ils ne peuvent pas être utilisés pour démontrer des formules mathématiques.

Le but de ce travail est de fournir un environnement uniforme au sein duquel des programmes peuvent être écrits, spéci{és, et prouvés. L'idée est de combiner un langage de programmation à la ML complet, avec un système de type enrichi pour permettre la spéci{cation de comportements calculatoires. Ce langage peut donc être utilisé comme ML pour programmer en tirant pro{t d'un typage statique fort, mais aussi comme un assistant à la preuve pour démontrer des propriétés de programmes ML. L'uniformité du système permet, en outre, de raf{ner les programmes petit à petit, pour obtenir de plus en plus de garanties. En particulier, il n'y a pas de distinction syntaxique entre les programmes et les preuves dans le système. On peut donc mélanger preuves et programmes durant la construction de preuves ou de programmes. Par exemples, on peut utiliser des mécanismes de preuve au sein de programmes a{n qu'ils portent des propriétés (par exemple, l'addition avec sa commutativité). Les programmes peuvent utiliser des mécanismes de preuve pour éliminer du code mort (ne pouvant pas être atteint à l'exécution).

Dans cette thèse, notre but premier est de mettre au point un système de type pour un langage de programmation fonctionnelle, utilisable en pratique. Parmi les nombreux choix techniques possibles, nous avons décidé de considérer un langage en appel par valeur similaire à OCaml ou SML, ces derniers ayant fait leurs preuves en terme d'ef{cacité et d'utilisation. Notre langage comporte des variants polymorphes [Garrigue 1998] et des types enregistrements à la SML, qui sont très pratiques pour encoder des types de données. Par exemple, le type des listes peut être dé{ni et utilisé de la manière suivante.

type rec lista = [Nil ; Cons of {hd : a ; tl : list}]

val rec exists : a, (a  bool)  lista  bool =

fun pred l { case l {

Nil  false

Cons[c]  if pred c.hd { true } else { exists pred c.tl } }

Ici, la fonction polymorpheexistsprend comme paramètre un prédicat et une liste, et elle indique si (au moins) un élément de la liste satisfait le prédicat.

Le système présenté ici n'est pas seulement un langage de programmation, mais aussi un assistant à la preuve, et en particulier à la preuve de programmes. Son mécanisme de preuve est basé sur des types égalités de la formet  u, oý tet usont des programmes arbitraires du langage. Un tel type égalité est habité par (ou contient) {} (c'est à dire l'enregistrement vide) si l'équivalence dénotée est vraie, et il est vide sinon. Les équivalences sont gérées en utilisant une procédure partielle de décision, qui est dirigée par la construc-tion de programmes. Un contexte d'équaconstruc-tions est maintenu par l'algorithme de typage, a{n de stocker les équivalences supposées correctes durant la construction de la preuve de typage. Ce contexte est étendu quand une nouvelle équation est apprise (par exemple, quand un lemme est appliqué), et une équation est prouvée en cherchant une contradiction (par exemple, quand deux variants di|férents sont supposés égaux).

Pour illustrer le fonctionnement des preuves, nous allons considérer l'exemple très simple des entiers naturels en représentation unaire (les nombres de Peano). Leur type est donné ci-dessous, avec la fonction d'addition correspondante, dé{nie par récurrence sur son premier argument.

type rec nat = [Zero ; Succ of nat]

val rec add : nat  nat  nat =

fun n m {

case n { Zero  m | Succ[k]  Succ[add k m] }

}

Comme premier exemple, nous allons montreradd Zero n  npour toutn. Pour exprimer cette propriété, on utilise le typen: , add Zero n  n, oý peut être vu comme l'ensemble de tous les programmes complètement évalués. Cette énoncé peut ensuite être démontré comme suit.

val add_z_n : n: , add Zero n  n = {}

Ici, la preuve est immédiate (c'est à dire,{}) commeadd Zero n  nse déduit directement de la dé{nition de la fonctionadd. Notez que cette équivalence est vraie pour toutn, qu'il corresponde à un élément de nat ou pas. Par exemple, on peut montrer sans problème l'équivalenceadd Zero true  true.

Regardons maintenant l'énoncé n: , add n Zero  n. Bien qu'il soit très similaire à

add_z_nen apparence, il ne peut pas être démontré. En e|fet, la relationadd n Zero  n

n'est pas vraie quandnn'est pas un entier unaire. Dans ce cas, l'évaluation deadd n Zero

quanti{cation dont le domaine se limite aux entiers unaires. Ceci peut être réalisé avec le type nnat, add n Zero  n, qui corresponds à une fonction (dépendante) prenant en entrée un entiern, et retournant une preuve de add n Zero  n. Cette propriété peut ensuite être prouvée en utilisant de l'induction (programme récursif) et une analyse par cas ({ltrage par motif).

val rec add_n_z : nnat, add n Zero  n =

fun n { case n {

Zero  {}

Succ[k]  let ih = add_n_z k; {} }

}

SinestZero, alors on doit montreradd Zero Zero  Zero, qui est immédiat par dé{nition deadd. Dans le cas oýnestSucc[k]on doit montreradd Succ[k] Zero  Succ[k]. Par dé{nition deadd, cette équation se réduit enSucc[add k Zero]  Succ[k]. Il suf{t donc de montreradd k Zero  ken utilisant l'hypothèse d'induction (add_n_z k).

Documents relatifs