Probleme – Curry--Howard : une preuve est un programme
Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 28 — Déduction naturelle
Énoncé
Le programme demande d'écrire de petites preuves ; ce problème montre qu'on en a déjà écrit beaucoup, sans le savoir, en programmant en OCaml. On pose la correspondance suivante entre formules et types :
| `'a -> 'b` | |
|---|---|
| `'a * 'b` | |
| `('a, 'b) somme`, avec `type ('a,'b) somme = G of 'a | D of 'b` | |
| `vide`, avec `type vide = |` (aucun constructeur) | |
| `'a -> vide` |
- Écrire un terme OCaml pour chacune des huit règles du chapitre.
- Écrire les termes correspondant à barbara, à l'affaiblissement et au syllogisme disjonctif, et lire leurs types inférés.
- Que devient la règle absurde de l'exercice 28.9 ?
- Quelle hypothèse la correspondance exige-t-elle des programmes ?
Corrigé
1. Les huit règles, en huit fonctions. Tout ce qui suit a été compilé et exécuté.
type vide = | (* le faux : un type SANS valeur *)
type ('a, 'b) somme = G of 'a | D of 'b
type 'a negation = 'a -> vide
let et_i a b = (a, b) (* et_i : Gamma|-A, Gamma|-B => A/\B *)
let et_e_g (a, _) = a (* et_e^g : le premier membre *)
let et_e_d (_, b) = b (* et_e^d : le second *)
let imp_i (f : 'a -> 'b) = f (* imp_i : l'abstraction EST la décharge *)
let imp_e (f : 'a -> 'b) (x : 'a) = f x (* imp_e : l'application EST le modus ponens *)
let ou_i_g a = G a
let ou_i_d b = D b
let ou_e s fg fd = match s with G a -> fg a | D b -> fd b (* le filtrage *)
let bot_e (x : vide) : 'c = match x with _ -> . (* le cas impossible *)
Les correspondances qui frappent : est la définition d'une fonction, est l'application, est la construction d'un couple, est une projection, et — la règle à trois prémisses, la plus lourde du chapitre — est le match à deux branches. La règle est le filtrage sur un type vide : le compilateur accepte match x with _ -> . précisément parce qu'il vérifie qu'aucun cas n'est possible.
2. Trois preuves, trois programmes. Types inférés par le compilateur, non annotés :
| séquent | terme | type inféré |
|---|---|---|
| barbara | `fun f g x -> g (f x)` | `('a->'b) -> ('b->'c) -> 'a -> 'c` |
| affaiblissement | `fun a _ -> a` | `'a -> 'b -> 'a` |
| `fun (a, b) -> (b, a)` | `'a * 'b -> 'b * 'a` |
Et le syllogisme disjonctif, qui emploie :
(* p \/ q, p |- q *)
let syllogisme_disjonctif s (np : 'a negation) =
match s with G a -> bot_e (np a) | D b -> b
Le type de fun f g x -> g (f x) est le syllogisme barbara, littéralement. La composition de fonctions et la transitivité de l'implication sont le même objet ; les trois nœuds de l'arbre du cours sont les trois symboles g, f et x. Et fun a _ -> a ignore son second argument, ce qui est exactement l'observation de l'exercice 28.4 : l'hypothèse ne sert pas.
3. La règle absurde n'a pas de programme. Son type serait (('a -> vide) -> vide) -> 'a, c'est-à-dire « d'une réfutation de la réfutation de , produire un ». Or produire un 'a quel que soit le type est impossible : il n'y a pas de valeur qui soit à la fois un entier, une chaîne et une fonction. Aucune fonction totale n'a ce type. L'absence de la règle du côté logique et l'absence du terme du côté programme sont le même fait.
Le pendant de le dit encore mieux : un terme de type ('a, 'a negation) somme devrait être G ou D, c'est-à-dire décider, pour tout type, s'il est habité ou non. Le tiers exclu est un programme qui répondrait à toute question — d'où son absence.
4. L'hypothèse cachée : les programmes doivent terminer. La correspondance ne tient que pour des termes totaux, sans exception ni boucle infinie. OCaml, lui, en autorise :
let rec absurde () = absurde () (* type inféré : unit -> 'a *)
Ce terme a le type unit -> 'a : il « démontre » n'importe quelle formule. Il ne rend simplement jamais de valeur. De même, assert false a le type 'a et prouve tout ce qu'on veut, en levant une exception.
La conclusion, et elle boucle sur le chapitre chap:algo-prog : une preuve est un programme qui termine. Un programme qui boucle est une preuve incorrecte, et la terminaison — le variant — n'est pas une propriété annexe, c'est ce qui donne au programme le droit d'être lu comme une preuve. C'est pourquoi les assistants de preuve comme Rocq (anciennement Coq) refusent toute récursion dont ils ne peuvent pas établir la terminaison. Tout ce chapitre est hors programme ; il n'est là que pour montrer que le formalisme qu'on vient d'apprendre est celui qu'on manipule depuis le chapitre chap:langage-ocaml.
Les autres exercices de ce chapitre Le cours du chapitre
Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.