40 votes

Comment fonctionne Djinn ?

OK, je réalise que je vais probablement le regretter pour le reste de ma vie, mais... Comment fonctionne réellement Djinn ?

La documentation indique qu'il utilise un algorithme qui est "une extension de LJ" et renvoie à un long document confus sur LJT. D'après ce que je sais, il s'agit d'un grand système compliqué de règles hautement formalisées permettant de déterminer quels énoncés logiques sont vrais ou faux. Mais cela ne fait même pas commencer pour expliquer comment transformer une signature de type en une expression exécutable. On peut supposer que tous les raisonnements formels compliqués sont impliqué d'une manière ou d'une autre, mais l'image est cruellement incomplète.


C'est un peu comme la fois où j'ai essayé d'écrire un interprète Pascal en BASIC. (Ne riez pas ! J'avais seulement douze ans...) J'ai passé des heures à essayer de comprendre, et à la fin j'ai dû abandonner. Je n'arrivais pas à comprendre comment on pouvait passer d'une chaîne géante contenant un programme entier à quelque chose que l'on peut comparer à des fragments de programme connus afin de décider ce qu'il faut faire.

La réponse, bien sûr, est que vous devez écrire une chose appelée "analyseur syntaxique". Une fois que vous comprenez ce que c'est et ce qu'il fait, tout devient soudain évident . Oh, ce n'est toujours pas trivial de le coder, mais la idée est simple. Il suffit d'écrire le code réel. Si j'avais su ce qu'étaient les analyseurs syntaxiques quand j'avais 12 ans, je n'aurais peut-être pas passé deux heures à regarder un écran blanc.

Je soupçonne que ce que fait Djinn est fondamentalement simple, mais il me manque un détail important qui explique comment toute cette gymnastique logique compliquée se rapporte au code source Haskell...

35voto

Philip JF Points 17248

Djinn est un vérificateur de théorèmes. Il semble que votre question soit : qu'est-ce que la preuve de théorème a à voir avec la programmation ?

La programmation fortement typée a une relation très étroite avec la logique. En particulier, les langages fonctionnels traditionnels de la tradition ML sont étroitement liés à la logique. Intuitionniste Logique propositionnelle .

Le slogan est "les programmes sont des preuves, la proposition qu'un programme prouve est son type".
En général, vous pouvez penser à

 foo :: Foo

comme disant que foo est une preuve de la formule Foo . Par exemple, le type

 a -> b  

correspond aux fonctions de a a b donc si vous avez une preuve de a et une preuve de a -> b vous avez une preuve de b . Ainsi, la fonction correspond parfaitement à l'implication en logique. De même,

(a,b)

Correspond à la conjonction (logique et). Donc la tautologie logique a -> b -> a & b correspond au type Haskell a -> b -> (a,b) et a la preuve :

\a b -> (a,b)

c'est la "règle de l'introduction" Alors que, fst :: (a,b) -> a y snd :: (a,b) -> b correspondent aux 2 "règles d'élimination"

de la même manière, a OR b correspond au type Haskell Either a b .

Cette correspondance est parfois appelée "isomorphisme de Curry-Howard" ou " Correspondance Curry-Howard "après Haskell Curry y William Alvin Howard

Cette histoire est compliquée par la non-totalité en Haskell.

Djinn est "juste" un vérificateur de théorèmes.

Si vous souhaitez essayer d'écrire un clone, la première page de résultats de Google pour "Simple Theorem Prover" contient les éléments suivants este qui décrit l'écriture d'un vérificateur de théorèmes pour LK qui semble être écrit en SML.

Edit : à la question "comment la preuve par le théorème est-elle possible ?" La réponse est que dans un certain sens, ce n'est pas difficile. C'est juste un problème de recherche :

Considérons le problème reformulé comme suit : nous avons un ensemble de propositions que nous savons prouver S, et une proposition que nous voulons prouver P. Que faisons-nous ? Tout d'abord, nous demandons : avons-nous déjà une preuve de P dans S ? Si oui, nous pouvons l'utiliser, sinon nous pouvons correspondance de motifs sur P

case P of
   (a -> b) -> add a to S, and prove b (-> introduction)
   (a ^ b)  -> prove a, then prove b (and introduction)
   (a v b)  -> try to prove a, if that doesn't work prove b (or introduction)

si aucune de ces solutions ne fonctionne

for each conjunction `a ^ b` in S, add a and b to S (and elimination)
for each disjunction `a v b` in S, try proving `(a -> P) ^ (b -> P)` (or elimination)
for each implication `a -> P` is S, try proving `a` (-> elimination)

Les vrais prouveurs de théorèmes ont un peu d'intelligence, mais l'idée est la même. Le domaine de recherche des "procédures de décision" examine la stratégie permettant de trouver des preuves pour certains types de formules dont le fonctionnement est garanti. D'autre part, la "tactique" examine comment ordonner de manière optimale la recherche de preuves.

Quant à : "Comment traduire les preuves en Haskell ?"

Chaque règle d'inférence d'un système formel correspond à une construction Haskell simple, donc si vous avez un arbre de règles d'inférence, vous pouvez construire un programme correspondant - Haskell est un langage de preuve après tout.

Introduction de l'implication :

\s -> ?

Ou introduction

Left
Right

Et introduction

\a b -> (a,b)

Et l'élimination

fst
snd

etc.

augustss dit dans sa réponse que la façon dont il a implémenté cela dans Djinn est un peu fastidieuse pour une réponse SO. Je parie cependant que vous pouvez trouver comment l'implémenter par vous-même.

23voto

augustss Points 15750

Dans les termes les plus généraux, selon l'isomorphisme de Curry-Howard, il existe une correspondance entre les types et les propositions, ainsi que les valeurs et les preuves. Djinn utilise cette correspondance.

Pour être plus concret, disons que vous voulez trouver un terme Haskell de type (a, b) -> (b, a) . Tout d'abord, vous traduisez le type en une déclaration en logique (Djinn utilise la logique propositionnelle, c'est-à-dire sans quantificateurs). L'énoncé logique est (A and B) is true implies (B and A) is true . L'étape suivante consiste à le prouver. En logique propositionnelle, il est toujours possible de prouver ou de réfuter mécaniquement un énoncé. Si nous pouvons la réfuter, cela signifie qu'il ne peut y avoir de terme correspondant en Haskell (terminant). Si nous pouvons le prouver, alors il existe un terme Haskell de ce type, et de plus, le terme Haskell a exactement la même structure que la preuve.

Cette dernière affirmation doit être nuancée. Il existe différents ensembles d'axiomes et de règles d'inférence que vous pouvez choisir d'utiliser pour prouver l'affirmation. Il n'y a de correspondance entre la preuve et le terme Haskell que si vous choisissez une logique constructive. La logique "normale", c'est-à-dire classique, comporte des éléments tels que A or (not A) comme un axiome. Cela correspondrait au type Haskell Either a (a -> Void) mais comme il n'existe pas de terme Haskell de ce type, nous ne pouvons pas utiliser la logique classique. Il est toujours vrai que tout énoncé propositionnel peut être prouvé ou réfuté en logique propositionnelle constructive, mais cela demande beaucoup plus de travail qu'en logique classique.

Pour résumer, Djinn traduit le type en une proposition logique, puis il utilise une procédure de décision en logique constructive pour prouver la proposition (si possible), et enfin la preuve est retraduite en un terme Haskell.

(Il est trop pénible d'illustrer comment cela fonctionne ici, mais donnez-moi 10 minutes devant un tableau blanc et ce sera clair comme de l'eau de roche pour vous).

Un dernier commentaire à méditer : Either a (a -> Void) peut être mis en œuvre si vous disposez de l'outil Scheme call/cc . Choisissez un type plus concret comme Either a (a -> Int) et trouver comment.

4voto

MathematicalOrchid Points 15354

Peut-être que je vois tout ça de travers. Peut-être que toute cette logique formelle n'est qu'une distraction. Plutôt que de regarder les règles de déduction pour LJT ou autre, peut-être que la chose que j'ai à faire devrait est de regarder Haskell.

Il y a, quoi, 6 types d'expression possibles en Haskell ? Et chacune d'entre elles impose des contraintes de type différentes sur les variables qu'elle utilise, non ? Donc, peut-être que je vais juste générer une nouvelle variable pour chaque argument du type de la fonction, et commencer à voir quelles expressions je peux construire.

Ce n'est même pas comme si vous deviez générer toutes les expressions possibles dans une recherche par force brute. Si aucun de vos arguments n'a de type de fonction, il est inutile d'essayer les applications de fonction. Si tous vos arguments sont des variables de type polymorphe, case Les expressions ne vont pas vous aider. Et ainsi de suite. Les types disponibles vous indiquent quelles sortes d'expressions peuvent fonctionner.

Les choses deviennent un peu plus intéressantes si vous autorisez votre code à appeler des fonctions de haut niveau existantes. En dehors des problèmes amusants de scoping avec les types polymorphes, il y a la question de savoir quelles fonctions vous aideront ou non.

Il est clair que je vais devoir m'absenter et réfléchir à tout cela pendant un certain temps...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X