Que savons-nous sur les programmes parfaitement corrects?


37

La complexité croissante des programmes informatiques et la position de plus en plus cruciale des ordinateurs dans notre société me laissent nous demander pourquoi nous n'utilisons toujours pas collectivement des langages de programmation dans lesquels vous devez prouver formellement que votre code fonctionne correctement.

Je crois que le terme est un "compilateur de certification" (je l'ai trouvé ici ): un compilateur compilant un langage de programmation dans lequel il est non seulement nécessaire d'écrire le code, mais aussi d'indiquer la spécification du code et de prouver que le code adhère à la spécification (ou utilisez un prouveur automatique pour le faire).

Lors de mes recherches sur Internet, je n'ai trouvé que des projets utilisant soit un langage de programmation très simple, soit des projets ayant échoué qui tentaient d'adapter les langages de programmation modernes. Cela m'amène à ma question:

Existe-t-il des compilateurs de certification implémentant un langage de programmation complet, ou est-ce très difficile / théoriquement impossible?

De plus, je n'ai encore vu aucune classe de complexité impliquant des programmes prouvables, telle que "la classe de toutes les langues décidables par une machine de Turing pour laquelle il existe une preuve que cette machine de Turing s'arrête", que j'appellerai ProvableR , en tant qu'analogue de R , l'ensemble des langages récursifs.

Je peux voir les avantages d'étudier une telle classe de complexité: par exemple, pour ProvableR le problème Stopper est décidable (je conjecture même définie de la manière évidente serait la plus grande classe de langues pour lesquelles il est décidable). De plus, je doute que nous excluions tous les programmes utiles: qui utiliserait un programme quand on ne peut pas prouver qu'il se termine?ProvableRE

Donc ma deuxième question est:

Que savons-nous des classes de complexité qui exigent que leurs langages contenus possèdent certaines propriétés?


1
Un compilateur pourrait énumérer toutes les preuves de longueur possibles, en passant de 1 à l’infini, jusqu’à ce qu’il trouve la preuve que le programme s’arrête. Si nous demandons que l'entrée du compilateur soit arrêtée de manière prouvée, le compilateur trouvera toujours cette preuve. Puisque le problème d’arrêt est indécidable, nous devons en conclure que certains programmes s’arrêtent, mais il n’en existe aucune preuve. Le point crucial est que les programmes sont incapables de savoir s’il existe une preuve, pas qu’ils soient incapables de trouver une preuve si elle existe.
Alex ten Brink

3
Je pense que vous devriez les séparer. Ce sont des questions différentes avec des réponses différentes.
Mark Reitblatt

4
Sur la première question, un article influent est "Processus sociaux et preuves de théorèmes et de programmes", portal.acm.org/citation.cfm?id=359106
Colin McQuillan, le

1
La vérification du programme est indécidable. Un problème est donc de dire ce qui constitue une bonne solution. Voir cstheory.stackexchange.com/questions/4016/…
Radu GRIGore le

2
@Colin: cet article mérite d'être lu pour l'analyse de la preuve, mais ses prédictions ont été falsifiées. Nous disposons aujourd'hui de compilateurs, de noyaux de systèmes d'exploitation, de collecteurs de mémoire et de bases de données parfaitement corrects et qui, selon eux, étaient impossibles. L'astuce pour échapper à leur critique consistait à éviter la vérification humaine des détails de bas niveau des preuves formelles, mais à utiliser la vérification à la machine des preuves et à utiliser des humains pour vérifier le vérificateur de preuves. Noam se réfère à la théorie des types là où se trouve l’état de la technique, ce qui laisse les programmes impératifs dans une impasse puisque la théorie des types est fonctionnelle.
Neel Krishnaswami

Réponses:


28

"Certifier le compilateur" signifie généralement quelque chose de légèrement différent: cela signifie que vous avez un compilateur qui peut prouver que le code machine qu'il émet correctement implémente la sémantique de haut niveau. C’est-à-dire que c’est la preuve qu’il n’ya pas de bogues de compilation. Les programmes que les utilisateurs donnent au compilateur peuvent toujours être erronés, mais le compilateur générera une version de code machine correcte du mauvais programme. La plus grande réussite de ce type est le compilateur vérifié CompCert , qui est un compilateur pour un grand sous-ensemble de C.

Le compilateur Compcert lui-même est un programme avec une preuve d'exactitude (réalisée en Coq), ce qui garantit que s'il génère du code pour un programme, il sera correct (en ce qui concerne la sémantique opérationnelle d'assembly & C utilisée par les concepteurs CompCert). L'effort de vérification de ces choses est assez grand; En règle générale, la preuve d'exactitude sera comprise entre 1x et 100x la taille du programme que vous vérifiez. Écrire des programmes et des épreuves vérifiés par machine est une nouvelle compétence que vous devez apprendre - ce ne sont pas des mathématiques ou une programmation comme d'habitude, même si cela dépend de la capacité de bien faire les deux. On a l'impression de repartir de zéro, de redevenir un programmeur débutant.

Il n'y a cependant pas d'obstacle théorique particulier à cela. La seule chose qui va dans ce sens est le théorème de Blum Size : pour toute langue dans laquelle tous les programmes sont totaux, vous pouvez trouver un programme dans une langue générale récursive qui sera au moins exponentiellement plus grand une fois programmé dans la langue totale. Pour comprendre ce résultat, une langue totale ne code pas uniquement un programme, mais également une preuve de résiliation. Ainsi, vous pouvez avoir des programmes courts avec des preuves de résiliation longues. Cependant, cela n'a pas vraiment d'importance dans la pratique, car nous n'allons jamais écrire que des programmes avec des preuves de terminaison gérables.

EDIT: Dai Le a demandé quelques précisions sur le dernier point.

Ceci est principalement une affirmation pragmatique, basée sur le fait que si vous pouvez comprendre pourquoi un programme fonctionne, il est alors peu probable que la raison en soit de vastes millions de pages invariantes. (Les invariants les plus longs que j'ai utilisés font quelques pages et font rire les critiques! C'est compréhensible aussi, puisque l'invariant est la raison pour laquelle le programme fonctionne, dépouillé de tout le récit qui aide les gens à le comprendre.)

Mais il y a aussi des raisons théoriques. En gros, nous ne connaissons pas beaucoup de façons d’inventer systématiquement des programmes dont les preuves de correction sont très longues. La méthode principale consiste à (1) prendre la logique dans laquelle vous apportez la preuve de la correction, (2) trouver une propriété qui ne peut pas être directement exprimée dans cette logique (les preuves de cohérence sont la source typique), et (3) trouver un programme dont La preuve de correction repose sur une famille de conséquences exprimables de la propriété inexprimable. Parce que (2) est inexprimable, cela signifie que la preuve de chaque conséquence exprimable doit être effectuée de manière indépendante, ce qui vous permet d'exploser la taille de la preuve de correction. Comme exemple simple, notez que dans la logique du premier ordre avec une relation parent, vous ne pouvez pas exprimer la relation ancêtre.kk) est exprimable, pour chaque fixé . Ainsi, en donnant un programme qui utilise certaines propriétés d'ascendance jusqu'à une certaine profondeur (par exemple, 100), vous pouvez forcer une preuve d'exactitude dans FOL à contenir cent fois les preuves de ces propriétés.k

Le point de vue sophistiqué sur ce sujet est appelé "mathématiques inversées" et consiste à étudier quels axiomes sont nécessaires pour prouver des théorèmes donnés. Je ne sais pas grand chose à ce sujet, mais si vous posez une question sur son application à CS, et je suis sûr qu'au moins Timothy Chow, et probablement plusieurs autres personnes, pourront vous dire des choses intéressantes.


1
Pourriez-vous s'il vous plaît préciser ce point "nous allons seulement écrire des programmes avec des preuves de terminaison gérables" un peu plus?
Dai Le

Merci pour votre réponse mise à jour! Votre réponse ouvre vraiment mon point de vue. En fait, je travaille moi aussi un peu sur les «mathématiques inversées», mais je n’ai pas réalisé le lien que vous avez mentionné. Merci encore!
Dai Le

1
L’argument de votre montage est lié au fait que nous n’avons pratiquement aucun candidat pour des tautologies qui nécessitent de longues preuves dans des systèmes de preuves naturelles (comme, par exemple, Frege). Cela tient en partie au fait que la seule façon dont nous savons qu'une tautologie est tautologue est tout d'abord parce que nous avions une preuve en tête, ce qui n'était pas nécessairement si long!
Joshua Grochow

22

Je pense que la réponse à la première question est qu’en général, c’est trop de travail avec les outils actuels. Pour en comprendre le sens, je suggère d'essayer de prouver l'exactitude de Bubble Sort dans Coq (ou, si vous préférez, un peu plus de défi, utilisez Tri rapide). Je ne pense pas qu'il soit raisonnable de s'attendre à ce que les programmeurs écrivent des programmes vérifiés tant que prouver l'exactitude de tels algorithmes de base est si difficile et prend beaucoup de temps.

Cette question revient à demander pourquoi les mathématiciens n'écrivent pas de preuves formelles vérifiables par des vérificateurs de épreuves. Écrire un programme avec une preuve formelle d’exactitude signifie prouver un théorème mathématique sur le code écrit, et la réponse à cette question s’applique également à votre question.

Cela ne signifie pas qu'il n'y a pas eu de cas réussis de programmes vérifiés. Je sais qu'il existe des groupes qui prouvent le bien-fondé de systèmes tels que l'hyperviseur de Microsoft . Un cas connexe est le compilateur C vérifié de Microsoft . Mais en général, les outils actuels ont besoin de beaucoup de développement (y compris leurs aspects SE et HCI) avant de devenir utiles pour les programmeurs généraux (et les mathématiciens).

En ce qui concerne le dernier paragraphe de la réponse de Neel sur la croissance de la taille du programme pour les langues ne comportant que des fonctions totales, il est en fait facile de prouver encore plus (si j'ai bien compris). Il est raisonnable de s’attendre à ce que la syntaxe de tout langage de programmation soit identique à celle de l’ensemble des fonctions calculables. Ainsi, pour tout langage de programmation où tous les programmes sont complets, il existe une fonction totale calculable qui ne peut être calculée par aucun programme ( de toute taille) dans cette langue.


Pour la deuxième question, j'ai déjà répondu à une question similaire sur le blog de Scott. Fondamentalement, si la classe de complexité a une belle caractérisation et peut être représentée de manière calculable (c'est-à-dire ce que c'est), nous pouvons alors prouver que certaines représentations des problèmes de la classe de complexité sont manifestement totales dans une théorie très faible correspondant à la classe de complexité. L’idée de base est que les fonctions prouvables de la théorie contiennent toutes les et un problème qui est A C 0AC0AC0-complete pour la classe de complexité, elle contient donc tous les problèmes de la classe de complexité et peut prouver la totalité de ces programmes. La relation entre les preuves et la théorie de la complexité est étudiée dans la complexité des preuves, voir le livre récent de SA Cook et P. Nguyen " Fondements logiques de la complexité de la preuve " si vous êtes intéressé. (Un brouillon de 2008 est disponible.) La réponse de base est donc que pour beaucoup de classes "Provably C = C".

Ce n'est pas vrai en général car il existe des classes de complexité sémantique qui ne possèdent pas de caractérisation syntaxique, par exemple des fonctions calculables totales. Si, par récursif, vous entendez des fonctions récursives totales, alors les deux ne sont pas égaux et l'ensemble des fonctions calculables qui sont vraisemblablement totales dans une théorie est bien étudié dans la littérature sur la théorie de la preuve et sont appelées les fonctions prouvables totales de la théorie. Par exemple: les fonctions prouvables de sont des fonctions récursives ϵ 0 (ou des fonctions équivalentes dans le système T de Godel ), les fonctions prouvables dePAϵ0T sont fonctionnelles dans le système F de Girard, les fonctions prouvables de totalPA2F sont des fonctions récursives primitives, ....IΣ1

Mais il ne me semble pas que cela signifie beaucoup dans le contexte de la vérification de programme, car il existe également des programmes qui calculent par extension la même fonction, mais nous ne pouvons pas prouver que les deux programmes calculent la même fonction, c'est-à-dire que les programmes sont par extension égaux mais non intentionnellement. (Cela ressemble à l’Étoile du matin et à l’Étoile du soir.) En outre, il est facile de modifier un programme total vérifiable pour en obtenir un dont la théorie est incapable de prouver sa totalité.


Je pense que les deux questions sont liées. L'objectif est d'obtenir un programme vérifié. Un programme vérifié signifie que le programme satisfait à une description, qui est un énoncé mathématique. Une solution consiste à écrire un programme dans un langage de programmation, puis à en prouver les propriétés, comme il satisfait à la description, ce qui est la pratique la plus courante. Une autre option consiste à essayer de prouver l'énoncé mathématique décrivant le problème à l'aide de moyens restreints, puis à en extraire un programme vérifié. Par exemple, si nous prouvons dans la théorie correspondant à que pour un nombre donné n, il existe une suite de nombres premiers dont le produit est égal à n , alors nous pouvons extraire un PPnnPalgorithme de factorisation à partir de la preuve. (Il existe également des chercheurs qui tentent d’automatiser autant que possible la première approche, mais la vérification des propriétés non triviales intéressantes des programmes est difficile en termes de calcul et ne peut pas être entièrement vérifiée sans faux positifs et négatifs.)


3
Bonne réponse! Vous avez mentionné que l'une des méthodes consiste à extraire des programmes à partir d'épreuves, ce que l'on peut faire automatiquement avec Coq, par exemple. Un domaine connexe est l' exploration de preuves , où des personnes (travaillant généralement en logique mathématique) essaient d'extraire des informations d'une preuve donnée. Par exemple, il est possible dans certains cas de trouver (automatiquement) une preuve intuitionniste à partir d'une preuve classique.
Radu GRIGore

1
@Radu: merci de signaler que cela peut être fait automatiquement en Coq. Comme vous l'avez mentionné, on peut également extraire des informations constructives et donc des programmes à partir de preuves classiques de théorèmes en utilisant l'extraction de preuves pour certaines classes de formules (les personnes intéressées peuvent consulter les papiers de Ulrich Kuhlenbach pour plus de détails). Un autre résultat possiblement lié est que si un théorème est prouvé en P A , il est également démontrable constructive en H A par la traduction de Harvey Friedman. _Π20PAHA
Kaveh le

1
Andrej Bauer a publié sur son blog un nouvel article intéressant dans lequel il prouve l'interprétation dialectique de Godel en Coq .
Kaveh

18

Ce que vous demandez dans la première question est parfois appelé un "compilateur de vérification", et il y a quelques années, Tony Hoare l'avait présenté comme un défi de taille pour la recherche informatique . Dans une certaine mesure, cela existe déjà et est activement utilisé dans des outils tels que le prouveur de théorèmes de Coq , qui organisent le problème par le biais de la théorie des types et du principe de propositions sous forme de types (" Curry-Howard ").

EDIT: je voulais juste mettre l'accent sur "dans une certaine mesure". Ce problème est loin d’être résolu, mais le succès de Coq laisse espérer que ce n’est pas une chimère.


8
Je dirais que la construction de logiciels vérifiés correspond à la construction de vieux logiciels simples en 1956. Il était déjà évident que les logiciels allaient être extrêmement importants, et il y avait déjà de grandes réussites. Cependant, il manquait encore aux personnes de nombreux concepts fondamentaux (par exemple, une compréhension claire des procédures et des variables) et la distance entre la théorie et la pratique pouvait être aussi courte que la mise en œuvre de Lisp en programmant le code dans un théorème. C’est une période extrêmement excitante pour travailler sur les langues et la vérification.
Neel Krishnaswami le

12

Un outil qui vérifie si un programme est correct est parfois appelé un vérificateur de programme. Dans ce contexte, "correct" signifie généralement deux choses: que le programme ne génère jamais certaines sorties (pensez à une erreur de segmentation, NullPointerException, etc.) et que le programme est conforme à une spécification.

Le code et la spécification peuvent concorder et être perçus comme erronés. En un sens, demander aux développeurs d’écrire des spécifications revient à demander à deux développeurs de résoudre le problème. Si les deux implémentations sont d’accord, vous avez alors une plus grande confiance dans leur acceptation. Dans un autre sens, cependant, les spécifications valent mieux qu'une seconde implémentation. Parce que la spécification n'a pas besoin d'être efficace ni même exécutable, elle peut être beaucoup plus succincte et donc plus difficile à se tromper.

Compte tenu de ces avertissements, je vous recommande de consulter le vérificateur de programme Spec # .


Pour autant que je comprenne Spec # (et son extension Sing #), cela donne au programmeur la possibilité de vérifier statiquement les assertions, mais cela n’exige pas que le programmeur le fasse, ni ne permet de prouver des propriétés arbitraires du code.
Alex ten Brink

Les propriétés arbitraires peuvent en principe être codées comme des assertions de type fol. Je ne suis pas sûr de ce que vous voulez que l'outil exige. Voulez-vous que les spécifications indiquent quelle devrait être la sortie pour toutes les entrées possibles?
Radu GRIGore

4

Dans le cas général, il est impossible de créer un algorithme qui confirme si un algorithme est équivalent à une spécification. Ceci est une preuve informelle:

Presque tous les langages de programmation sont Turing-complete. Par conséquent, toute langue choisie par une MT peut également l'être par un programme écrit dans cette langue.

Eqvousjevunelence/TM

Eqvousjevunelence/TMNon-emptjeness/TMEmptjeness/TMEmptjeness/TMEqvousjevunelence/TMEqvousjevunelence/TMest également inacceptable. Par conséquent, vous pouvez utiliser un algorithme, que deux machines ne soient pas équivalentes ou non, mais vous ne pouvez pas savoir si elles sont équivalentes ou si vous n'avez pas donné à votre algorithme suffisamment de temps.

Cependant, ce n'est que pour le cas général. Il est possible que vous puissiez décider si les spécifications sont équivalentes au programme, en résolvant une version plus détendue du problème. Par exemple, vous pouvez n’examiner que plusieurs entrées ou dire que les deux programmes sont équivalents avec une certaine incertitude. C’est ce que sont les tests logiciels.

Pour le reste de vos questions:

Remarque: cette partie a été modifiée pour clarification. Il se trouve que j'ai commis l'erreur que j'essayais d'éviter, désolé.

TrvouseRTrvouseRR

ProvunebleR=TrvouseRProvunebleRTrvouseRTrvouseRProvunebleR. La preuve est très simple: laissezUNEεTrvouseR. Par définition, dans chaque entréeUNErenvoie OUI ou NON. Par conséquent,UNEs'arrête à chaque entrée. Il s'ensuit queUNEεProvunebleR .

De manière informelle, ceci peut être résumé comme suit: Vous ne savez pas qu’une langue est décidable tant que vous n’avez pas prouvé qu’elle est. Donc, si dans un système formel vous avez la connaissance qu'une langue est décidable, cette connaissance peut également servir de preuve à cela. Par conséquent, vous ne pouvez pas savoir simultanément qu’une langue est décidable et qu’elle ne peut pas être prouvée. Ces deux déclarations s’excluent mutuellement.

Mon dernier point est que notre connaissance est partielle et que la seule façon d'étudier R est à travers ProvunebleR. La distinction peut avoir un sens en mathématiques et nous pouvons le reconnaître, mais nous ne possédons aucun outil pour l’étudier. PuisqueProvunebleRR , nous sommes tenus de ne jamais savoir complètement R dans son intégralité.

@Kaveh résume bien ce qui suit: L'expression «prouvable» signifie toujours prouvable dans un système / théorie donné et ne correspond pas à la vérité en général.

Il en va de même pour toute autre classe de complexité: pour déterminer l’appartenance, vous devez d’abord une preuve. C'est pourquoi je pense que votre deuxième question est trop générale, car elle contient non seulement la théorie de la complexité, mais également la théorie des calculs, en fonction de la propriété que vous souhaitez que le langage ait.


1
Ce peu à propos de RProvunebleRsemble un peu louche. Pourquoi faut-il une preuve pour qu'une langue soit décidable? En fait, il semble qu'il n'y en ait pas (en général): déterminer si le jeu en arrêt d'une MT est un langage récursif estΣ30Achevée. S'il y avait une preuve que le jeu d'arrêt d'un TM était récursif, ce serait enΣ10.
Mark Reitblatt

1
Prouvable signifie toujours prouvable dans un système / théorie et ne coïncide pas avec la vérité en général.
Kaveh

1
Je vois maintenant que pour que ma question soit intéressante, il faut parler de l'ensemble des machines à l'arrêt de Turing, et non de l'ensemble des langages décidables.
Alex ten Brink

1
@ Alex Eh bien, vous avez besoin d'un moyen de parler des langues, mais il y en a énormément. Ainsi, si vous souhaitez parler de langues connectées à un objet fini (comme une preuve), vous devez vous limiter aux langues identifiables par un objet fini, comme une MT.
Mark Reitblatt le

2
chazisop, comme je l'ai dit, prouvable signifie par une théorie (fixe), et pour toute théorie raisonnable (ce, cohérente, ...), il existe une fonction calculable totale qui ne peut être prouvée comme étant totale dans cette théorie. Il y a des fonctions totales dont il est impossible de prouver qu'elles sont totales dans ZFC (et des extensions raisonnables de celles-ci). Vous confondez la notion de vérité avec la faisabilité. Donc, la preuve ne marche pas (en supposant queR signifie total récursif).
Kaveh

3

La monographie classique suivante étudie presque exactement votre deuxième question:

Hartmanis, J. Calculs réalisables et propriétés de complexité prouvables , Série de conférences régionales CBMS-NSF en mathématiques appliquées, 30. Société de mathématiques industrielles et appliquées (SIAM), Philadelphie, Pennsylvanie, 1978.

Par exemple, il définit la classe "F-TIME [T (n)]" (où F est un système de preuve formelle raisonnable) comme étant {L(Mje)|Tje(n)T(n) est prouvable en F} (où Mje est la i-ème machine de Turing dans une liste complète de MT, et Tje(n) est la durée maximale de Mje sur les entrées de longueur n). Let me just highlight a few results relevant to your question - many more can be found in the monograph and references therein:

Cor. 6.4: Let T(n)nlog(n) be a computable function, and let g(n)1 be an unbounded computable function. Then F-TIME[T(n)]TIME[T(n)g(n)].

Theorem 6.5: There exist computable monotonically increasing time bounds T(n) for which F-TIME[T(n)]TIME[T(n)].

Theorem 6.14: For any computable T(n)nlog(n), TIME[T(n)]={L(Mi)|F proves(j)[L(Mj)=L(Mi)Tj(n)T(n)]}.

For space, however, the situation is better controlled:

Theorem 6.9: (1) If s(n)n is space-constructible, then SPACE[s(n)]=F-SPACE[s(n)].

(2) If SPUNECE[S(n)]=F-SPUNECE[S(n)] (S(n)n) alors il y a un espace-constructible s(n) tel que SPUNECE[S(n)]=SPUNECE[s(n)].


1

La question doit être posée correctement. Par exemple, personne ne veut jamais savoir si un programme réel achèverait une mémoire infinie et un moyen de l’accéder (par exemple, une opération permettant de déplacer l’adresse de base d’un nombre). Le théorème de Turing n'a aucune pertinence concrète pour le programme correct et les personnes qui le citent comme un obstacle à la vérification du programme confondent deux choses très différentes. Lorsque les ingénieurs / programmeurs parlent d'exactitude de programme, ils veulent en savoir plus sur les propriétés finies. Ceci est également vrai pour les mathématiciens qui souhaitent savoir si quelque chose est prouvable. La lettre de Godel http://vyodaiken.com/2009/08/28/godels-lost-letter/ l' explique en détail.

Cela signifierait évidemment que, malgré l’indécidabilité du problème d’Entscheidungs, le travail mental d’un mathématicien concernant les questions du type oui ou non pourrait être complètement remplacé par une machine. Après tout, il faudrait simplement choisir le nombre naturel n si grand que, lorsque la machine ne donne pas de résultat, il n’a aucun sens de réfléchir davantage au problème.

Il peut être impossible d’examiner l’immense ensemble d’états d’un programme s’exécutant sur un ordinateur réel et de détecter les mauvais états. Il n’existe aucune raison théorique pour ne pas le faire. En fait, beaucoup de progrès ont été accomplis dans ce domaine - par exemple, voir http://www.cs.cornell.edu/gomes/papers/SATSolvers-KR-book-draft-07.pdf (merci à Neil Immerman pour me parler de ça)

Un problème différent et plus difficile consiste à spécifier exactement les propriétés qu'un programme doit avoir pour être correctes.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.