Programmer des démonstrations : une modeste invitation aux assistants de preuve
En principe, une dĂ©monstration mathĂ©matique ne fait que suivre des rĂšgles logiques bien dĂ©finies, et devrait donc pouvoir ĂȘtre encodĂ©e informatiquement et vĂ©rifiĂ©e par un ordinateur. OĂč en est-on dans la pratique et dans la thĂ©orie ? Petit tour au pays des assistants de preuve, des langages de programmation dĂ©diĂ©s aux dĂ©monstrations, et de leur fondement thĂ©orique le plus commun, la thĂ©orie des types.
- lien ná” 1 : Why formalize mathematics? par Patrick Massot
Sommaire
- VĂ©rifier des programmes
- Vérifier des démonstrations mathématiques
- Brouwer-Heyting-Kolmogorov
- Curry-Howard
- Quantificateurs et types dépendants
- Logique intuitionniste
- Quelques exemples
- Quelques succĂšs de la formalisation
- La recherche en théorie des types
- Conclusion
VĂ©rifier des programmes
Comme nous sommes sur LinuxFr.org, je devrais peut-ĂȘtre commencer par ceci : nous passons Ă©normĂ©ment de temps Ă dĂ©couvrir des bugs, et pour les personnes du dĂ©veloppement logiciel, Ă les comprendre, Ă les rĂ©soudre, et de prĂ©fĂ©rence Ă les Ă©viter en Ă©crivant des tests.
Dans une formation universitaire de base en informatique, on rencontre des algorithmes, mais aussi des mĂ©thodes pour prouver que ces algorithmes terminent et rĂ©pondent bien au problĂšme posĂ©. Les premiĂšres introduites sont typiquement les variants de boucle (montrer quâune certaine valeur dĂ©croĂźt Ă chaque itĂ©ration, ce qui assure que le programme termine si elle ne peut pas dĂ©croĂźtre Ă lâinfini), et les invariants de boucle (montrer quâune certaine propriĂ©tĂ© vraie au dĂ©but dâune boucle est prĂ©servĂ©e entre deux itĂ©rations, ce qui assure quâelle reste encore vraie Ă la fin de la boucle).
On a donc, dâune part, un algorithme, implĂ©mentable sur machine, dâautre part une preuve, sur papier, que lâalgorithme est correct. Mais si lâimplĂ©mentation a une erreur par rapport Ă lâalgorithme sur papier ? Et puisque nous nâarrĂȘtons pas de nous tromper dans nos programmes, il est fort possible que nous nous trompions dans notre preuve (qui nâa jamais oubliĂ© quâil fallait faire quelque chose de spĂ©cial dans le cas ?).
En tant que programmeurs, on peut imaginer une approche oĂč non seulement lâalgorithme est implĂ©mentĂ©, mais sa preuve de terminaison et de correction est aussi « implĂ©mentĂ©e », câest-Ă -dire encodĂ©e dans un langage qui ressemble Ă un langage de programmation, pour ĂȘtre ensuite non pas interprĂ©tĂ©e ou compilĂ©e mais vĂ©rifiĂ©e.
La vĂ©rification est un trĂšs vaste domaine de lâinformatique, dont je ne suis pas spĂ©cialiste du tout, et dans lequel il existe Ă©normĂ©ment dâapproches : la logique de Hoare (voir par exemple lâoutil why3), qui est essentiellement un raffinement des variants et invariants de boucle, la logique de sĂ©paration spĂ©cialement conçue pour raisonner sur la mĂ©moire mutable (voir Iris), le model checking qui se concentre sur des programmes dâune forme particuliĂšrement simple (typiquement des systĂšmes de transition finis) pour en vĂ©rifier des propriĂ©tĂ©s de façon complĂštement automatisĂ©e, etc.
Dans cette dĂ©pĂȘche, je vais parler dâune approche avec quelques caractĂ©ristiques particuliĂšres :
On vĂ©rifie des programmes purement fonctionnels (pas dâeffets de bord, mĂȘme si on peut les simuler).
Le mĂȘme langage mĂ©lange Ă la fois les programmes et leurs preuves.
Plus précisément, le langage ne fait pas (ou peu) de distinction entre les programmes et les preuves.
Vérifier des démonstrations mathématiques
Pour se convaincre de lâampleur que les dĂ©monstrations ont prise dans les mathĂ©matiques contemporaines, il suffit dâaller jeter un Ćil, par exemple, au projet Stacks : un livre de rĂ©fĂ©rence sur la gĂ©omĂ©trie algĂ©brique, Ă©crit collaborativement sur les 20 derniĂšres annĂ©es, dont lâintĂ©grale totalise plus de 7500 pages trĂšs techniques. Ou bien la dĂ©monstration du thĂ©orĂšme de classification des groupes finis simples : la combinaison de rĂ©sultats rĂ©partis dans les dizaines de milliers de pages de centaines dâarticles, et une preuve « simplifiĂ©e » toujours en train dâĂȘtre Ă©crite et qui devrait faire plus de 5000 pages. Ou bien le thĂ©orĂšme de Robertson-Seymour, monument de la thĂ©orie des graphes aux nombreuses applications algorithmiques : 20 articles publiĂ©s sur 20 ans, 400 pages en tout. Ou bien, tout simplement, le nombre de rĂ©fĂ©rences dans la bibliographie de la moindre thĂšse ou dâarticles publiĂ©s rĂ©cemment sur arXiv.
InĂ©vitablement, beaucoup de ces dĂ©monstrations contiennent des erreurs. Parfois dĂ©couvertes, parfois beaucoup plus tard. Un exemple assez cĂ©lĂšbre est celui dâun thĂ©orĂšme, qui aurait Ă©tĂ© trĂšs important sâil avait Ă©tĂ© vrai, publiĂ© en 1989 par Vladimir Voedvodsky, un cĂ©lĂšbre mathĂ©maticien dont je vais ĂȘtre amenĂ© Ă reparler plus bas, avec Mikhail Kapranov. Comme racontĂ© par Voedvodsky lui-mĂȘme, un contre-exemple a Ă©tĂ© proposĂ© par Carlos Simpson en 1998, mais jusquâen 2013, Voedvodsky lui-mĂȘme nâĂ©tait pas sĂ»r duquel Ă©tait faux entre sa preuve et le contre-exemple !
Il y a aussi, souvent, des « trous », qui ne mettent pas tant en danger la dĂ©monstration mais restent gĂȘnants : par exemple, « il est clair que la mĂ©thode classique de compactification des espaces Lindelöf sâapplique aussi aux espaces quasi-Lindelöf », quand lâauteur pense Ă©vident quâun argument existant sâadapte au cas dont il a besoin mais que ce serait trop de travail de le rĂ©diger entiĂšrement. Donc, assez naturellement, un but possible de la formalisation des maths est de produire des dĂ©monstrations qui soient certifiĂ©es sans erreur (et sans trou).
Mais câest loin dâĂȘtre le seul argument. On peut espĂ©rer dâautres avantages, qui pour lâinstant restent de la science-fiction, mais aprĂšs tout ce nâest que le dĂ©but : par exemple, on peut imaginer que des collaborations Ă grande Ă©chelle entre beaucoup de mathĂ©maticiens deviennent possibles, grĂące au fait quâil est beaucoup plus facile de rĂ©utiliser le travail partiel de quelquâun dâautre sâil est formalisĂ© que sâil est donnĂ© sous formes dâĂ©bauches informelles pas complĂštement rĂ©digĂ©es.
Brouwer-Heyting-Kolmogorov
Parmi les assistants de preuve existants, la plupart (mais pas tous) se fondent sur une famille de systĂšmes logiques rangĂ©s dans la famille des « thĂ©ories des types ». Lâune des raisons pour lesquelles ces systĂšmes sont assez naturels pour ĂȘtre utilisĂ©s en pratique est quâen thĂ©orie des types, les preuves et les programmes deviennent entiĂšrement confondus ou presque, ce qui rend facile le mĂ©lange entre les deux.
Mais comment est-ce quâun programme devient une preuve, au juste ? LâidĂ©e de base est appelĂ©e interprĂ©tation de Brouwer-Heyting-Kolomogorov et veut que les preuves mathĂ©matiques se comprennent de la façon suivante :
Le moyen de base pour prouver une proposition de la forme «
et
» est de fournir dâune part une preuve de
et une preuve de
. En dâautres mots, une preuve de «
et
» rassemble en un mĂȘme objet une preuve de
et une preuve de
. Mais en termes informatiques, ceci signifie quâune preuve de «
et
» est une paire dâune preuve de
et dâune preuve de
.
De mĂȘme, pour prouver «
ou
», on peut prouver
, ou on peut prouver
. Informatiquement, une preuve de «
ou
» va ĂȘtre une union disjointe : une preuve de
ou une preuve de
, avec un bit pour savoir dans quel cas on est.
Pour prouver « Vrai », il suffit de dire « câest vrai » : on a une unique preuve de « Vrai ».
On ne doit pas pouvoir prouver « Faux », donc une preuve de « Faux » nâexiste pas.
Et le plus intéressant : pour prouver « si
alors
», on suppose temporairement
et on en déduit
. Informatiquement, ceci doit devenir une fonction qui prend une preuve de
et renvoie une preuve de
.
Curry-Howard
LâinterprĂ©tation de Brouwer-Heyting-Kolmogorov est informelle, et il existe plusieurs maniĂšres de la rendre formelle. Par exemple, on peut interprĂ©ter tout ceci par des programmes dans un langage complĂštement non-typĂ©, ce qui sâappelle la rĂ©alisabilitĂ©.
Mais en thĂ©orie des types, on prend plutĂŽt un langage statiquement typĂ© pour suivre lâidĂ©e suivante : si une preuve de et
est une paire dâune preuve de
et dâune preuve de
, alors le type des paires de
et
peut se comprendre comme le type des preuves de «
et
». On peut faire de mĂȘme avec les autres types de preuves, et ceci sâappelle la correspondance de Curry-Howard. Autrement dit, lĂ oĂč Brouwer-Heyting-Kolmogorov est une correspondance entre les preuves et les programmes, Curry-Howard est un raffinement qui met aussi en correspondance les propositions logiques avec les types du langage, et la vĂ©rification des preuves se confond entiĂšrement avec le type checking.
Sur les cas que jâai donnĂ©s, la correspondance de Curry-Howard donne :
La proposition «
et
» est le type des paires dâun Ă©lĂ©ment de
et dâun Ă©lĂ©ment de
,
La proposition «
ou
» est le type somme de
et
(comme
Either
en Haskell et OCaml, les tagged unions en C, etstd::variant
en C++ : lâun ou lâautre, avec un boolĂ©en pour savoir lequel),La proposition « Vrai » est le type trivial Ă une seule valeur (comme
()
en Haskell et Rust,unit
en OCaml),La proposition « Faux » est le type vide qui nâa aucune valeur (comme
!
en Rust),La proposition « si
alors
» est le type des fonctions qui prennent un argument de type
et renvoient une valeur de type
.
Quantificateurs et types dépendants
La version de Curry-Howard que jâai esquissĂ©e donne une logique dite « propositionnelle » : il nây a que des propositions, avec des connecteurs entre elles. Mais en maths, on ne parle Ă©videmment pas que des propositions. On parle de nombres, de structures algĂ©briques, dâespaces topologiques, âŠ, bref, dâobjets mathĂ©matiques, et des propriĂ©tĂ©s de ces objets. Les deux types principaux de propositions qui manquent sont ce quâon appelle les quantificateurs : « Pour tout , ⊠» et « Il existe
tel que⊠». Ici, ce qui est une évidence en logique devient moins évident, mais trÚs intéressant, du cÎté des programmes.
Prenons pour lâexemple le thĂ©orĂšme des deux carrĂ©s de Fermat, qui Ă©nonce (dans lâune de ses variantes) quâun nombre premier impair est de la forme si et seulement sâil peut sâĂ©crire comme somme de deux carrĂ©s parfaits. Ă quoi doit ressembler le type associĂ© Ă cette proposition ? Par analogie avec les implications, on a envie de dire que cela devrait ĂȘtre une fonction, qui prend un nombre premier impair
, et renvoie une preuve de lâĂ©quivalence. ProblĂšme : ce qui est Ă droite de lâĂ©quivalence est une proposition paramĂ©trĂ©e par
. Autrement dit, en notant
le type des nombres premiers impairs, on ne veut plus un simple type fonction
, mais un type fonction oĂč le type de retour peut dĂ©pendre de la valeur passĂ©e Ă la fonction, notĂ© par exemple
. Ces types qui dépendent de valeurs sont appelés types dépendants.
Dans les langages de programmation populaires, il est rare de trouver des types dépendants. Mais on en retrouve une forme faible en C avec les tableaux de longueur variable (VLA pour « variable-length arrays ») : on peut écrire
⊠f(int n) {
int array[n];
âŠ
}
qui déclare un tableau dont la taille n
est une expression. NĂ©anmoins, en C, mĂȘme si on dispose de ce type tableau qui est en quelque sorte dĂ©pendant, on ne peut pas Ă©crire une fonction « int[n] f(int n)
» qui renvoie un tableau dont la longueur est passĂ©e en paramĂštre. Plus rĂ©cemment, en Rust, il existe les const generics, oĂč des valeurs se retrouvent dans les types et oĂč on peut Ă©crire fn f<const n: usize>() -> [u8; n]
, ce qui est un vrai type dĂ©pendant, mais cette fois avec la restriction assez sĂ©vĂšre que toutes ces valeurs peuvent ĂȘtre calculĂ©es entiĂšrement Ă la compilation, ce qui Ă cause de la façon dont fonctionne ce type de calcul en Rust empĂȘche par exemple les allocations mĂ©moire. (Donc lâimplĂ©mentation est assez diffĂ©rente, elle efface ces valeurs en « monomorphisant » tous les endroits oĂč elles apparaissent.)
En thĂ©orie des types, le langage est (normalement) purement fonctionnel, donc les problĂšmes dâeffets de bord dans les valeurs Ă lâintĂ©rieur des types dĂ©pendants ne se pose pas. Le type checking peut dĂ©clencher des calculs arbitrairement complexes pour calculer les valeurs qui se trouvent dans les types.
Et le « il existe », au fait, Ă quoi correspond-il ? Cette fois, ce nâest plus une fonction dĂ©pendante mais une paire dĂ©pendante : une preuve de « Il existe tel que
» est une paire dâune valeur
et dâune preuve de
. La différence avec une paire normale est que le type du deuxiÚme élément peut dépendre de la valeur du premier élément.
Comme on peut commencer Ă sâen douter, le fait dâavoir des types dĂ©pendants est utile pour prouver des affirmations mathĂ©matiques, mais aussi, bien quâil puisse sembler inhabituel, pour prouver des programmes, et plus prĂ©cisĂ©ment pour encoder des propriĂ©tĂ©s des valeurs dans les types. LĂ oĂč on aurait dans un langage moins expressif une fonction qui renvoie deux listes, avec une remarque dans la documentation quâelles sont toujours de mĂȘme taille, dans un langage avec des types dĂ©pendants, on peut renvoyer un triplet dâun entier , dâune liste dont le type indique quâelle est de taille
, et une deuxiĂšme liste elle aussi de taille
. Et lĂ oĂč on aurait un
deuxieme_liste[indice_venant_de_la_premiere]
avec un commentaire que cela ne peut pas produire dâerreur, car les deux listes sont de mĂȘme taille, on a un programme qui utilise la garantie que les deux listes sont de mĂȘme taille, et le typage garantit statiquement que cette opĂ©ration t[i]
ne produira pas dâerreur.
Logique intuitionniste
Reprenons lâexemple du thĂ©orĂšme des deux carrĂ©s de Fermat. Nous pouvons maintenant traduire cette proposition en un type : celui des fonctions qui prennent un nombre premier impair et renvoient une paire de :
Une fonction qui prend
tel que
et renvoie deux entiers
accompagnĂ©s dâune preuve que
,
RĂ©ciproquement, une fonction qui prend
et une preuve de
, et renvoie
tel que
.
Prouver le thĂ©orĂšme des deux carrĂ©s de Fermat en thĂ©orie des types, câest donner un Ă©lĂ©ment (on dit plutĂŽt « habitant ») de ce type, soit un programme dont le langage peut vĂ©rifier quâil a ce type. Mais que fait au juste ce programme quand on lâexĂ©cute ? On voit quâil permet notamment de calculer une dĂ©composition dâun nombre premier impair congru Ă 1 modulo 4 comme somme de deux carrĂ©s.
LĂ , câest le cĂŽtĂ© « programmes » qui apporte un Ă©lĂ©ment moins habituel du cĂŽtĂ© « preuves » : lâexĂ©cution dâun programme va correspondre Ă un processus de simplification des preuves. Notamment, si on a une preuve de « si alors
» et une preuve de
, on peut prendre la preuve de
et remplacer chaque utilisation de lâhypothĂšse
dans la preuve de « si
alors
», pour obtenir une preuve de
qui peut contenir de multiples copies dâune mĂȘme preuve de
. Cette opération de simplification du cÎté logique correspond naturellement au fait que la maniÚre en théorie des types de prouver
Ă partir dâune preuve
de
et dâune preuve
de
est tout simplement dâĂ©crire
, et que calculer
, informatiquement, se fait bien en remplaçant le paramÚtre de
Ă tous les endroits oĂč il apparaĂźt par la valeur
et Ă simplifier le rĂ©sultat. On dit que la logique de Curry-Howard est constructive, parce quâelle se prĂȘte Ă une interprĂ©tation algorithmique.
Mais ceci peut sembler gĂȘnant. Par exemple, il est trivial en maths « normales » de prouver que tout programme termine ou ne termine pas. Mais par Curry-Howard, une preuve que tout programme termine ou ne termine pas doit ĂȘtre une fonction qui prend un programme, et qui renvoie soit une preuve quâil termine, soit une preuve quâil ne termine pas. Autrement dit, si cette proposition censĂ©ment triviale Ă©tait prouvable dans Curry-Howard, on aurait un algorithme pour rĂ©soudre le problĂšme de lâarrĂȘt, ce qui est bien connu pour ĂȘtre impossible.
Lâexplication Ă cette diffĂ©rence tient au fait que la preuve « triviale » de cette proposition utilise une rĂšgle de dĂ©duction qui a un statut un peu Ă part en logique, dite rĂšgle du tiers exclu : pour nâimporte quelle proposition , sans aucune hypothĂšse, on peut dĂ©duire «
ou (non
) » (autrement dit, que
est vraie ou fausse). Or cette rĂšgle nâadmet pas dâinterprĂ©tation Ă©vidente par Curry-Howard : le tiers exclu devrait prendre une proposition
et renvoyer soit une preuve de
, soit une preuve que
est fausse (ce qui sâencode par « si
alors Faux »), autrement dit, le tiers exclu devrait ĂȘtre un oracle omniscient capable de vous dire si une proposition arbitraire est vraie ou fausse, et ceci est bien Ă©videmment impossible.
(Cela dit, si vous voulez vous faire mal Ă la tĂȘte, apprenez que câest lâopĂ©rateur call/cc et pourquoi lâajouter permet de prouver le tiers exclu. Exercice : call/cc existe dans de vrais langages, comme Scheme, pourtant on vient de voir que le tiers exclu semble nĂ©cessiter un oracle omniscient, comment expliquer cela ?)
Pour ĂȘtre prĂ©cis, la logique sans le tiers exclu est dite intuitionniste (le terme constructive Ă©tant un peu flou, alors que celui-ci est prĂ©cis). On peut faire des maths en restant entiĂšrement en logique intuitionniste, et mĂȘme si ce nâest pas le cas de lâĂ©crasante majoritĂ© des maths, il existe tout de mĂȘme un certain nombre de chercheurs qui le font, et ceci peut avoir divers intĂ©rĂȘts. Il y a notamment lâinterprĂ©tation algorithmique des thĂ©orĂšmes, mais aussi, de maniĂšre beaucoup plus avancĂ©e, le fait que certaines structures mathĂ©matiques (topos, â-topos et consorts) peuvent sâinterprĂ©ter comme des sortes dâunivers mathĂ©matiques alternatifs rĂ©gis par les rĂšgles de la logique intuitionniste (techniquement, des « modĂšles » de cette logique), et que parfois il est plus simple de prouver un thĂ©orĂšme en le traduisant Ă lâintĂ©rieur de lâunivers et en prouvant cette version traduite de maniĂšre intuitionniste.
Pour pouvoir malgrĂ© tout raisonner en thĂ©orie des types de maniĂšre classique (par opposition Ă intuitionniste), il suffit de postuler le tiers exclu comme axiome. Du point de vue des programmes, cela revient Ă rajouter une constante qui est supposĂ©e avoir un certain type mais qui nâa pas de dĂ©finition (cela peut donc rendre les programmes impossibles Ă exĂ©cuter, ce qui est normal pour le tiers exclu).
Quelques exemples
Si vous aviez dĂ©crochĂ©, câest le moment de reprendre. Parlons un peu des assistants de preuve qui existent. Les plus connus sont :
Rocq, anciennement nommĂ© Coq, dĂ©veloppĂ© Ă lâInria depuis 1989, Ă©crit en OCaml, sous licence LGPL 2.1. Il est assez liĂ© Ă lâhistoire de la thĂ©orie des types, car il a Ă©tĂ© crĂ©Ă© par Thierry Coquand comme premiĂšre implĂ©mentation du calcul des constructions, une thĂ©orie des types inventĂ©e par Coquand et devenue lâune des principales existantes. (Oui, Coq a Ă©tĂ© renommĂ© en Rocq Ă cause de lâhomophonie en anglais entre « Coq » et « cock ». JâapprĂ©cierais que les commentaires ne se transforment pas en flame war sur ce sujet trĂšs peu intĂ©ressant, merci.)
Lean, créé par Leonardo de Moura et développé depuis 2013 chez Microsoft Research, écrit en C++, placé sous licence Apache 2.0.
Agda, créé par Ulf Norrell en 1999, écrit en Haskell et sous licence BSD 1-clause.
Dâautres que je connais moins, notamment Isabelle et F* (liste sur WikipĂ©dia).
Pour illustrer comment peuvent fonctionner les choses en pratique, voici un exemple trĂšs simple de code en Agda :
open import Agda.Primitive using (Level)
open import Data.Product using (_Ă_; _,_)
swap : {ââ ââ : Level} â {P : Set ââ} {Q : Set ââ} â P Ă Q â Q Ă P
swap (p , q) = (q , p)
Vue comme un programme, cette fonction swap
inverse simplement les deux Ă©lĂ©ments dâune paire. Vue comme une preuve, elle montre que pour toutes propositions et
, si
et
, alors
et
. Comme le veut Curry-Howard, les deux ne sont pas distingués. Les types
et
sont eux-mĂȘmes dans des types
avec un « niveau »
, ceci parce que, pour des raisons logiques, il serait incohérent que le type des types soit de son propre type, donc on a un premier type de types
, qui est lui-mĂȘme de type
, et ainsi de suite avec une hiĂ©rarchie infinie de niveaux appelĂ©s univers. Ă un niveau plus superficiel, on remarquera quâAgda a une syntaxe qui ressemble fort Ă Haskell (et utilise intensivement Unicode).
VoilĂ la mĂȘme chose en Rocq :
Definition swap {P Q : Prop} : P /\ Q -> Q /\ P :=
fun H => match H with conj p q => conj q p end.
La syntaxe est assez différente et ressemble plutÎt à OCaml (normal, vu que Rocq est écrit en OCaml et Agda en Haskell). Mais à un niveau plus profond, on voit apparaßtre un type Prop
dont le nom Ă©voque furieusement les propositions. Or jâavais promis que les propositions seraient confondues avec les types, donc pourquoi a-t-on un type spĂ©cial pour les propositions ?
En rĂ©alitĂ©, pour diverses raisons, il peut ĂȘtre intĂ©ressant de briser lâanalogie dâorigine de Curry-Howard et de sĂ©parer les propositions et les autres types en deux mondes qui se comportent de façon extrĂȘmement similaire mais restent nĂ©anmoins distincts. Notamment, un principe quâon applique sans rĂ©flĂ©chir en maths est que si deux propositions sont Ă©quivalentes, alors elles sont Ă©gales (extensionnalitĂ© propositionnelle), mais on ne veut clairement pas ceci pour tous les types (on peut donner des fonctions bool -> int
et int -> bool
, pourtant on ne veut certainement pas bool = int
), donc sĂ©parer les propositions des autres permet dâajouter lâextensionnalitĂ© propositionnelle comme axiome. (Mais il y a aussi des diffĂ©rences comme l'imprĂ©dicativitĂ© dans lesquelles je ne vais pas rentrer.)
Et voici encore le mĂȘme code, cette fois en Lean :
def swap {P Q : Prop} : P ⧠Q â Q ⧠P :=
fun âšp, qâ© => âšq, pâ©
Ă part les diffĂ©rences de syntaxe, câest trĂšs similaire Ă Rocq, parce que Lean a aussi une sĂ©paration entre les propositions et les autres types.
Cependant, en Rocq et Lean, on peut aussi prouver la mĂȘme proposition de façon diffĂ©rente :
Lemma swap {P Q : Prop} : P /\ Q -> Q /\ P.
Proof.
intros H. destruct H as [p q]. split.
- apply q.
- apply p.
Qed.
et
def swap {P Q : Prop} : P ⧠Q â Q ⧠P := by
intro h
have p := h.left
have q := h.right
exact âšq, pâ©
Avec Proof.
ou by
, on entre dans un mode oĂč les preuves ne sont plus Ă©crites Ă la main comme programmes, mais avec des tactiques, qui gĂ©nĂšrent des programmes. Il existe toutes sortes de tactiques, pour appliquer des thĂ©orĂšmes existants, raisonner par rĂ©currence, rĂ©soudre des inĂ©galitĂ©s, ou mĂȘme effectuer de la recherche automatique de dĂ©monstration, ce qui sâavĂšre extrĂȘmement utile pour simplifier les preuves.
Ce mode « tactiques » permet aussi dâĂ©crire la preuve de façon incrĂ©mentale, en faisant un point dâĂ©tape aprĂšs chaque tactique pour voir ce qui est prouvĂ© et ce qui reste Ă prouver. Voici par exemple ce quâaffiche Rocq aprĂšs le destruct
et avant le split
:
P, Q : Prop
p : P
q : Q
============================
Q /\ P
Cette notation signifie que le contexte ambiant contient les variables P
et Q
de type Prop
ainsi que p
une preuve de P
(donc un élément du type P
) et q
une preuve de Q
. Le Q /\ P
en dessous de la barre horizontale est le but Ă prouver, câest-Ă -dire le type dont on cherche Ă construire un Ă©lĂ©ment.
Agda fonctionne assez diffĂ©remment : il nây a pas de tactiques, mais il existe nĂ©anmoins un systĂšme de mĂ©ta-programmation qui sert Ă faire de la recherche de preuves (donc contrairement Ă Rocq et Lean, on nâĂ©crit pas la majeure partie des preuves avec des tactiques, mais on peut se servir dâun Ă©quivalent quand câest utile). Pour Ă©crire les preuves incrĂ©mentalement, on met ?
dans le programme quand on veut ouvrir une sous-preuve, et Agda va faire le type-checking de tout le reste et donner le contexte Ă lâendroit du ?
.
Quelques succĂšs de la formalisation
En 2025, la formalisation reste trÚs fastidieuse, mais elle a déjà eu plusieurs grands succÚs :
- Le théorÚme des quatre couleurs, formalisé en Rocq (2005).
- Le théorÚme de Feit-Thompson (étape dans la fameuse classification des groupes finis simples) en Rocq (2012).
- Un théorÚme de Peter Scholze sur les « espaces vectoriels liquides » en Lean (2022).
- Le retournement de la sphĂšre en Lean (2022).
- La valeur de BB(5) en Rocq (2024).
Actuellement, Lean a rĂ©ussi Ă attirer une communautĂ© de mathĂ©maticiens qui dĂ©veloppent mathlib (1,1 million de lignes de code Lean au moment oĂč jâĂ©cris), une bibliothĂšque de dĂ©finitions et thĂ©orĂšmes mathĂ©matiques qui vise Ă ĂȘtre la plus unifiĂ©e possible.
Les Ă©quivalents dans dâautres assistants de preuve se dĂ©veloppent mĂȘme sâils ne sont pas (encore) aussi gros : citons mathcomp, unimath, agda-unimath entre autres.
Un autre grand succÚs, dans le domaine de la vérification cette fois, est CompCert (malheureusement non-libre), qui est un compilateur C entiÚrement écrit en Rocq et vérifié par rapport à une spécification du C également encodée en Rocq.
La recherche en théorie des types
La théorie des types est un domaine de recherche à part entiÚre, qui vise à étudier du point de vue logique les théories des types existantes, et à en développer de nouvelles pour des raisons à la fois théoriques et pratiques.
Historiquement, une grande question de la thĂ©orie des types est celle de comprendre Ă quel type doivent correspondre les propositions dâĂ©galitĂ©. Par exemple, on veut que deux propositions Ă©quivalentes soient Ă©gales, et que deux fonctions qui prennent les mĂȘmes valeurs soient Ă©gales, et Ă©ventuellement pour diverses raisons que deux preuves de la mĂȘme proposition soient Ă©gales, mais sâil est facile dâajouter toutes ces choses comme axiomes, il est trĂšs compliquĂ© de les rendre prouvables sans obtenir, comme avec le tiers exclu, des programmes qui ne peuvent pas sâexĂ©cuter Ă cause des axiomes qui sont dĂ©clarĂ©s sans dĂ©finition.
Vladimir Voedvodsky a fait une contribution majeure en proposant un nouvel axiome, appelĂ© univalence, qui dit trĂšs sommairement que si deux types ont la mĂȘme structure (on peut donner une « Ă©quivalence » entre les deux), alors ils sont en fait Ă©gaux (rĂ©sumĂ© simpliste Ă ne pas prendre au mot). Cet axiome est trĂšs pratique pour faire des maths parce quâon travaille souvent avec des objets qui ont la mĂȘme structure (on dit quâils sont isomorphes), et qui doivent donc avoir les mĂȘmes propriĂ©tĂ©s, et cet axiome permet de les identifier (mĂȘme sâil a aussi des consĂ©quences qui peuvent paraĂźtre choquantes). Sa proposition a donnĂ© naissance Ă une branche appelĂ©e thĂ©orie homotopique des types, qui explore les maths avec univalence. Le prix Ă payer est que les types ne se comprennent plus comme de simples ensembles de valeurs (ou de preuves dâune proposition), mais comme des espaces gĂ©omĂ©triques munis de toute une structure complexe (techniquement, les Ă©galitĂ©s sont des chemins entre points, et il y a des Ă©galitĂ©s non-triviales, des Ă©galitĂ©s entre Ă©galitĂ©s, etc.), et la comprĂ©hension de ces espaces-types est fondĂ©e sur la thĂ©orie de lâhomotopie. Il y a bien dâautres thĂ©ories des types, avec univalence ou non : thĂ©orie cubique des types, thĂ©orie des types observationnelle, etc.
Conclusion
JâespĂšre avoir communiquĂ© un peu de mon enthousiasme pour le domaine (dans lequel je suis probablement parti pour dĂ©marrer une thĂšse). Si vous voulez apprendre un assistant de preuve, une ressource assez abordable est la sĂ©rie Software Foundations avec Rocq. Il existe Ă©galement Theorem proving in Lean 4 et divers tutoriels Agda. Vous pouvez aussi essayer ces assistants de preuve directement dans votre navigateur : Rocq, Lean ou Agda. Et bien sĂ»r les installer et jouer avec : Rocq, Lean, Agda.
Commentaires : voir le flux Atom ouvrir dans le navigateur