Qu'est-ce qu'une preuve d'exactitude pour un vérificateur de typage devrait réellement prouver?

11

Je programme depuis plusieurs années, mais je ne connais pas très bien le CS théorique. J'ai récemment essayé d'étudier les langages de programmation, et dans le cadre de cela, la vérification de type et l'inférence.

Ma question est, si j'essaie d'écrire un programme d'inférence de type et de vérification d'un langage de programmation, et que je souhaite prouver que mon vérificateur de typage fonctionne, quelle est exactement la preuve que je recherche?

En langage clair, je veux que mon vérificateur de type puisse identifier les erreurs dans un morceau de code qui pourraient se produire au moment de l'exécution. Si je devais utiliser quelque chose comme Coq pour essayer de prouver que mon implémentation est correcte, qu'est-ce que cette "preuve d'exactitude" tentera de montrer?

Vivek Ghaisas
la source
Peut-être pouvez-vous préciser si vous voulez savoir (1) si votre implémentation implémente un système de typage , ou (2) si votre système de typage empêche les erreurs que vous pensez qu'il devrait? Ce sont des questions différentes. TTT
Martin Berger
1
@MartinBerger: Ah, il me semble avoir ignoré cette différence. Ma véritable question visait probablement à poser les deux. Le contexte est que j'essaye de construire un langage, et pour cela j'écrivais un vérificateur de frappe. Et les gens m'ont demandé d'utiliser un algorithme éprouvé. J'étais intéressé de voir à quel point il serait difficile de «prouver» que l'algorithme et le vérificateur de frappe que j'utilisais étaient «corrects». D'où l'ambiguïté de ma question.
Vivek Ghaisas
2
(1) est vraiment une question de vérification de programme et n'a pas grand-chose à voir avec la saisie. Il suffit de montrer que votre implémentation répond à ses spécifications. Quant à (2), définissez d'abord ce que signifie être une erreur de type immédiate (par exemple, des termes comme 2 + "hello"celui-ci sont «bloqués»). Une fois ceci formalisé, vous pouvez alors prouver le théorème de la solidité du type. Cela signifie qu'aucun programme typable ne peut jamais évoluer vers une erreur de type immédiate. Formellement, vous prouvez que si un programme est typable, et pour tout : si exécute étapes pour devenir , alors n'a pas d'erreur de type immédiate. (1/2)n M n N NMnMnNN
Martin Berger
1
Ceci est typiquement prouvé par induction sur et sur la dérivation du judegement de typage. (2/2)n
Martin Berger
Je vous remercie! D'après votre explication, il semble que (2) soit effectivement ce que je cherchais. Pourriez-vous s'il vous plaît répondre à cela? (Et peut-être ajouter des détails qui pourraient vous être utiles.) J'accepterais cela comme réponse! :)
Vivek Ghaisas

Réponses:

10

La question peut être interprétée de deux manières:

  • Si l'implémentation met en œuvre un système de typage ?T
  • Si le système de dactylographie empêche les erreurs que vous pensez qu'il devrait?T

Le premier est vraiment une question de vérification de programme et n'a pas grand-chose à voir avec la saisie. Il suffit de montrer que votre implémentation répond à ses spécifications, voir la réponse d'Andrej.

Permettez-moi de parler de la dernière question. Comme Andrej l'a dit, d'un point de vue abstrait, un système de typage semble imposer des propriétés aux programmes. En pratique, votre système de dactylographie cherche à empêcher les erreurs de se produire, ce qui signifie que les programmes typables ne doivent pas présenter la classe des erreurs d'intérêt. Afin de montrer que fait ce que vous pensez qu'il devrait, vous devez faire deux choses.TTT

  • Tout d'abord, vous définissez formellement ce que signifie pour un programme une erreur de frappe immédiate . Il y a plusieurs façons de définir cela - c'est à vous de décider. En règle générale, nous voulons empêcher des programmes comme 2 + "hello". En d'autres termes, vous devez définir un sous-ensemble de programmes, appelé Bad , qui contient exactement les programmes avec une erreur de frappe immédiate.

  • Vous devez ensuite prouver que les programmes typables ne peuvent jamais évoluer en programmes en Bad . Formalisons cela. Laissez votre jugement de frappe être Rappelons que cela doit être lu comme suit: le programme a le type , en supposant que les variables libres sont typées comme donné par l'environnement . Alors le théorème que vous voulez prouver est:M α ΓΓM:α.MαΓ

    Théorème. Chaque fois que et puis Bad .M N N ΓM:αMNN

    Comment prouver ce théorème dépend des détails de la langue, du système de frappe et de votre choix de Bad .

Un moyen standard de définir Bad est - à - dire: un terme a une erreur de type immédiat si elle est ni une valeur , ni a une étape de réduction . (Dans ce cas, est souvent appelé bloqué .) Cela ne fonctionne que pour la sémantique opérationnelle à petite étape. Une façon standard de prouver le théorème est de montrer queM N MMMNM

  • M N Γ N : αΓM:α et impliquent ensemble . C'est ce qu'on appelle la "réduction du sujet". Elle est généralement prouvée par une induction simultanée sur la dérivation du jugement de typage et la longueur des réductions.MNΓN:α

  • Chaque fois que alors n'est pas en mauvais état . Ceci est généralement également prouvé par induction sur la dérivation du jugement de typage.MΓM:αM

Notez que tous les systèmes de frappe n'ont pas de "réduction de sujet", par exemple des types de session. Dans ce cas, des techniques de preuve plus sophistiquées sont nécessaires.

Martin Berger
la source
20

C'est une bonne question! Il demande ce que nous attendons des types dans un langage tapé.

Notez d'abord que nous pouvons taper n'importe quel langage de programmation avec l' unité : il suffit de choisir une lettre, de dire Uet de dire que chaque programme a du type U. Ce n'est pas terriblement utile, mais cela fait un point.

Il existe de nombreuses façons de comprendre les types, mais du point de vue du programmeur, je pense que ce qui suit est utile. Considérez un type comme une spécification ou une garantie . Dire que a le type revient à dire que "nous garantissons / attendons / demandons que satisfasse la propriété codée par ". Souvent, est quelque chose de simple , auquel cas la propriété est simplement "c'est un entier".A e A AeAeAAint

Il n'y a pas de fin à la façon dont vos types peuvent être expressifs. En principe, il peut s'agir de n'importe quel type d'énoncés logiques, ils peuvent utiliser la théorie des catégories, etc. Vous pouvez aller plus loin, en ce moment j'écoute une conférence sur les "logiques de séparation simultanées" qui vous permet de parler du fonctionnement des programmes simultanés avec un état partagé. Des trucs de fantaisie.

L'art des types dans la conception de langages de programmation est un équilibre entre expressivité et simplicité :

  • des types plus expressifs nous permettent d'expliquer plus en détail (à nous-mêmes et au compilateur) ce qui est censé se passer
  • les types plus simples sont plus faciles à comprendre et peuvent être automatisés plus facilement dans le compilateur. (Les gens proposent des types qui nécessitent essentiellement un assistant de vérification et la saisie de l'utilisateur pour effectuer la vérification de type.)

La simplicité ne doit pas être sous-estimée, car tous les programmeurs n'ont pas de doctorat en théorie des langages de programmation.

Revenons donc à votre question: comment savez-vous que votre système de typage est bon ? Eh bien, prouvez des théorèmes qui montrent que vos types sont équilibrés. Il y aura deux types de théorèmes:

  1. Théorèmes qui disent que vos types sont utiles . Savoir qu'un programme a un type devrait impliquer certaines garanties, par exemple que le programme ne restera pas bloqué (ce serait un théorème de sécurité ). Une autre famille de théorèmes relierait les types à des modèles sémantiques afin que nous puissions commencer à utiliser de vrais mathématiques pour prouver des choses sur nos programmes (ce seraient des théorèmes d'adéquation , et bien d'autres). L'unité ci-dessus est mauvaise car elle n'a pas de théorèmes aussi utiles.

  2. Théorèmes qui disent que vos types sont simples . Un élément fondamental serait qu'il est possible de déterminer si une expression donnée a un type donné. Une autre fonctionnalité de simplicité consiste à fournir un algorithme pour déduire un type. D'autres théorèmes sur la simplicité seraient: qu'une expression a au plus un type, ou qu'une expression a un type principal (c'est-à-dire, le "meilleur" parmi tous les types qu'elle a).

Il est difficile d'être plus précis car les types sont un mécanisme très général. Mais j'espère que vous voyez ce que vous devez viser. Comme la plupart des aspects de la conception d'un langage de programmation, il n'y a pas de mesure absolue de réussite. Au lieu de cela, il y a un espace de possibilités de conception, et l'important est de comprendre où dans l'espace vous êtes, ou voulez être.

Andrej Bauer
la source
Merci pour cette réponse détaillée! Cependant, je ne suis toujours pas sûr de la réponse à ma question. Comme exemple concret, prenons C - un langage typé statiquement avec un système de type assez simple. Si j'écrivais un vérificateur de frappe pour C, comment prouver que mon vérificateur de frappe est «correct»? Comment cette réponse change-t-elle si à la place j'écris un vérificateur de type pour Haskell, disons HM? Comment pourrais-je maintenant prouver la «correction»?
Vivek Ghaisas
1
TeATeA
Je recommanderais de faire 2. et 3. en combinaison. Jetez également un œil à CompCert .
Andrej Bauer
1
TeAeAe
AAe
5

Il y a quelques choses différentes que vous pourriez dire par "prouver que mon vérificateur de typage fonctionne". Ce qui, je suppose, fait partie de ce que demande votre question;)

La moitié de cette question prouve que votre théorie des types est suffisamment bonne pour prouver les propriétés du langage. La réponse d'Andrej aborde très bien ce domaine imo. L'autre moitié de la question est - en supposant que la langue et son système de type sont déjà fixes - comment pouvez-vous prouver que votre vérificateur de type particulier met en œuvre correctement le système de type? Il y a deux perspectives principales que je peux voir ici.

La première est: comment pouvons-nous jamais croire qu'une implémentation particulière correspond à ses spécifications? Selon le degré d'assurance que vous souhaitez, vous pouvez être satisfait d'une grande suite de tests, ou vous pouvez vouloir une sorte de vérification formelle, ou plus probablement un mélange des deux . L'avantage de cette perspective est qu'elle met vraiment en évidence l'importance de fixer des limites aux revendications que vous faites: que signifie exactement «corriger»? quelle partie du code est vérifiée, par rapport à quelle partie est le TCB supposé correct? etc. L'inconvénient est que trop réfléchir à cela mène à des trous de lapin philosophiques - enfin, "inconvénient" si vous n'aimez pas ces trous de lapin.

La deuxième perspective est une interprétation plus mathématique de l'exactitude. Lorsque nous traitons des langues en mathématiques, nous mettons souvent en place des "modèles" pour nos "théories" (ou vice versa), puis essayons de prouver: (a) tout ce que nous pouvons faire dans la théorie, nous pouvons le faire dans le modèle, et (b) tout ce que nous pouvons faire dans le modèle, nous pouvons le faire dans la théorie. (Ce sont la solidité et l' exhaustivitéthéorèmes. Lequel qui dépend de si vous "avez commencé" à partir de la théorie syntaxique ou du modèle sémantique.) Avec cet état d'esprit, nous pouvons penser que votre implémentation de vérification de type est un modèle particulier pour la théorie de type en question. Donc, vous voudriez prouver cette correspondance bidirectionnelle entre ce que votre implémentation peut faire et ce que la théorie dit que vous devriez pouvoir faire. L'avantage de cette perspective est qu'elle se concentre vraiment sur la question de savoir si vous avez couvert tous les cas d'angle, si votre implémentation est terminée dans le sens de ne pas laisser de programmes qu'elle devrait accepter comme de type sécurisé et si votre implémentation est solide dans le sentiment de ne laisser entrer aucun programme qu'il devrait rejeter comme mal typé. L'inconvénient est que votre preuve de correspondance est susceptible d'être assez séparée de la mise en œuvre elle-même,

wren romano
la source
Je ne suis pas sûr d'être d'accord avec "l'avantage de cette perspective, c'est qu'elle se concentre vraiment sur la question de savoir si vous avez couvert tous les cas d'angle", surtout si le modèle n'est que solide, mais pas complet. Je proposerais une perspective différente: passer par un modèle est une technique de preuve contingente que vous utilisez pour diverses raisons, par exemple parce que le modèle est plus simple. Il n'y a rien de plus philosophiquement digne de passer par un modèle - en fin de compte, vous voulez en savoir plus sur l'exécutable réel et son comportement.
Martin Berger
Je pensais que «modèle» et «théorie» signifiaient au sens large, et wren soulignait simplement l'importance d'essayer d'établir une correspondance bidirectionnelle via un «théorème de solidité + complétude». (Je pense également que cela est important et j'ai fait un commentaire au message d'Andrej.) Il est vrai que dans certaines situations, nous ne pourrons prouver qu'un théorème de solidité (ou un théorème d'exhaustivité, selon votre point de vue), mais ayant les deux directions à l'esprit est une contrainte méthodologique utile.
Noam Zeilberger
1
@NoamZeilberger "La question est," a dit Martin, "si vous pouvez faire en sorte que les mots signifient tant de choses différentes."
Martin Berger
Lorsque j'ai découvert les systèmes de typage et la sémantique des langages de programmation, j'ai découvert que les modèles ne sont que des techniques de preuve de la sémantique opérationnelle, plutôt que des fins en soi, sublimement libérateurs.
Martin Berger
1
Relier différents modèles à travers la solidité et l'exhaustivité est une méthodologie scientifique importante pour le transfert de connaissances.
Martin Berger