Je suis tombé sur l' isomorphisme de Curry-Howard relativement tard dans ma vie de programmation, ce qui contribue peut-être à ce que je sois totalement fasciné par celui-ci. Cela implique que pour chaque concept de programmation, il existe un analogue précis dans la logique formelle, et vice versa. Voici une liste "de base" de ces analogies, par surprise:
program/definition | proof
type/declaration | proposition
inhabited type | theorem/lemma
function | implication
function argument | hypothesis/antecedent
function result | conclusion/consequent
function application | modus ponens
recursion | induction
identity function | tautology
non-terminating function | absurdity/contradiction
tuple | conjunction (and)
disjoint union | disjunction (or) -- corrected by Antal S-Z
parametric polymorphism | universal quantification
Donc, à ma question: quelles sont certaines des implications les plus intéressantes / obscures de cet isomorphisme? Je ne suis pas logicien, donc je suis sûr que je n'ai fait qu'effleurer la surface avec cette liste.
Par exemple, voici quelques notions de programmation pour lesquelles je ne connais pas les noms pithy en logique:
currying | "((a & b) => c) iff (a => (b => c))"
scope | "known theory + hypotheses"
Et voici quelques concepts logiques que je n'ai pas tout à fait épinglés en termes de programmation:
primitive type? | axiom
set of valid programs? | theory
Éditer:
Voici quelques équivalences supplémentaires recueillies à partir des réponses:
function composition | syllogism -- from Apocalisp
continuation-passing | double negation -- from camccann
la source
goto | jumping to conclusions
Réponses:
Puisque vous avez explicitement demandé les plus intéressants et les plus obscurs:
Vous pouvez étendre CH à de nombreuses logiques et formulations de logiques intéressantes pour obtenir une très grande variété de correspondances. Ici, j'ai essayé de me concentrer sur certains des plus intéressants plutôt que sur l'obscur, ainsi que sur quelques éléments fondamentaux qui ne sont pas encore apparus.
EDIT: Une référence que je recommanderais à tous ceux qui souhaitent en savoir plus sur les extensions de CH:
"A Judgmental Reconstruction of Modal Logic" http://www.cs.cmu.edu/~fp/papers/mscs00.pdf - c'est un bon point de départ car il part des premiers principes et une grande partie est destinée à être accessible aux non-logiciens / théoriciens du langage. (Je suis le deuxième auteur, donc je suis partial.)
la source
Vous embrouillez un peu les choses concernant la non-terminaison. La fausseté est représentée par des types inhabités , qui par définition ne peuvent pas être sans terminaison car il n'y a rien de ce type à évaluer en premier lieu.
La non-résiliation représente une contradiction - une logique incohérente. Une logique incohérente vous permettra bien sûr de prouver quoi que ce soit , y compris la fausseté, cependant.
Ignorant les incohérences, les systèmes de types correspondent généralement à une logique intuitionniste et sont par nécessité constructivistes , ce qui signifie que certains éléments de la logique classique ne peuvent pas être exprimés directement, voire pas du tout. D'un autre côté, c'est utile, car si un type est une preuve constructive valide, alors un terme de ce type est un moyen de construire tout ce dont vous avez prouvé l'existence .
Une caractéristique majeure de la saveur constructiviste est que la double négation n'est pas équivalente à la non-négation. En fait, la négation est rarement une primitive dans un système de types, donc à la place nous pouvons la représenter comme impliquant le mensonge, par exemple,
not P
devientP -> Falsity
. La double négation serait donc une fonction avec type(P -> Falsity) -> Falsity
, ce qui n'est clairement pas équivalent à quelque chose de type justeP
.Cependant, il y a une tournure intéressante à ce sujet! Dans un langage à polymorphisme paramétrique, les variables de type s'étendent sur tous les types possibles, y compris les types inhabités, donc un type entièrement polymorphe tel que
∀a. a
, dans un certain sens, est presque faux. Et si nous écrivions une double quasi-négation en utilisant le polymorphisme? Nous obtenons un type qui ressemble à ceci:∀a. (P -> a) -> a
. Est-ce que cela équivaut à quelque chose de typeP
? En fait , il suffit de l'appliquer à la fonction d'identité.Mais à quoi ça sert? Pourquoi écrire un type comme ça? Cela signifie- t-il quelque chose en termes de programmation? Eh bien, vous pouvez le considérer comme une fonction qui a déjà quelque chose de type
P
quelque part, et qui a besoin que vous lui donniez une fonction qui prendP
comme argument, le tout étant polymorphe dans le type de résultat final. En un sens, il représente un calcul suspendu , attendant que le reste soit fourni. En ce sens, ces calculs suspendus peuvent être composés ensemble, transmis, invoqués, peu importe. Cela devrait commencer à sembler familier aux fans de certaines langues, comme Scheme ou Ruby - parce que cela signifie que la double négation correspond au style de continuation-pass, et en fait le type que j'ai donné ci-dessus est exactement la monade de continuation dans Haskell.la source
P -> Falsity
. Je comprends pourquoi cela fonctionne (¬p ≡ p → ⊥), mais je ne reçois pas la version du code.P -> ⊥
devrait être habité précisément quand ceP
n'est pas le cas, non? Mais cette fonction ne devrait-elle pas toujours être habitée? Ou possible jamais, en fait, puisque vous ne pouvez pas renvoyer une instance de⊥
? Je n'en vois pas tout à fait la conditionnalité. Quelle est l'intuition ici?P -> Falsity
équivaut àP
être fausse.f x = x
, qui serait instanciable siP = ⊥
, mais ce n'était clairement pas assez générique. L'idée est donc que pour renvoyer un type sans valeur, vous n'avez pas besoin de corps; mais pour que la fonction soit définissable et totale, vous n'avez pas besoin de cas , et donc s'ilP
est inhabité, tout fonctionne? C'est un peu bancal, mais je pense que je le vois. Cela semble interagir assez bizarrement avec ma définition duXor
type… Je vais devoir y réfléchir. Merci!Votre graphique n'est pas tout à fait exact; dans de nombreux cas, vous avez confondu les types et les termes.
[1] La logique d'un langage fonctionnel Turing-complet est incohérente. La récursivité n'a pas de correspondance dans les théories cohérentes. Dans une logique incohérente / théorie de la preuve non valable, vous pouvez l'appeler une règle qui provoque une incohérence / un défaut de validité.
[2] Encore une fois, c'est une conséquence de l'exhaustivité. Ce serait une preuve d'un anti-théorème si la logique était cohérente - donc, elle ne peut pas exister.
[3] N'existe pas dans les langages fonctionnels, car ils élident les caractéristiques logiques du premier ordre: toute quantification et paramétrisation se fait sur des formules. Si vous aviez des fonctionnalités de premier ordre, il y aurait une autre espèce que
*
,* -> *
, etc .; le genre d'éléments du domaine du discours. Par exemple, dansFather(X,Y) :- Parent(X,Y), Male(X)
,X
etY
portée sur le domaine du discours (appelerDom
), etMale :: Dom -> *
.la source
la source
J'aime vraiment cette question. Je ne sais pas grand-chose, mais j'ai quelques petites choses (assisté par l'article de Wikipédia , qui contient des tableaux soignés et autres):
Je pense que les types de somme / types d'union ( par exemple
data Either a b = Left a | Right b
) sont équivalents à la disjonction inclusive . Et, bien que je ne connaisse pas très bien Curry-Howard, je pense que cela le démontre. Considérez la fonction suivante:Si je comprends bien les choses, le type dit que ( a ∧ b ) → ( a ★ b ) et la définition dit que c'est vrai, où ★ est soit inclusif, soit exclusif ou, selon ce que
Either
représente. Vous avezEither
représentant exclusif ou, ⊕; cependant, ( a ∧ b ) ↛ ( a ⊕ b ). Par exemple, ⊤ ∧ ⊤ ≡ ⊤, mais ⊤ ⊕ ⊥ ≡ ⊥ et ⊤ ↛ ⊥. En d'autres termes, si a et b sont tous les deux vrais, alors l'hypothèse est vraie mais la conclusion est fausse, et donc cette implication doit être fausse. Cependant, clairement, ( a ∧ b ) → ( a ∨ b ), puisque si a et b sont tous les deux vrais, alors au moins un est vrai. Ainsi, si les unions discriminées sont une forme de disjonction, elles doivent être la variété inclusive. Je pense que c'est une preuve, mais je me sens plus que libre de me désabuser de cette notion.De même, vos définitions de la tautologie et de l'absurdité en tant que fonction d'identité et fonctions non terminales, respectivement, sont un peu décalées. La vraie formule est représentée par le type d'unité , qui est le type qui n'a qu'un seul élément (
data ⊤ = ⊤
; souvent orthographié()
et / ouUnit
dans les langages de programmation fonctionnels). Cela a du sens: puisque ce type est garanti d'être habité, et comme il n'y a qu'un seul habitant possible, cela doit être vrai. La fonction d'identité représente simplement la tautologie particulière que a → a .Votre commentaire sur les fonctions sans terminaison est, en fonction de ce que vous vouliez précisément dire, plus offensif. Curry-Howard fonctionne sur le système de types, mais la non-terminaison n'y est pas codée. Selon Wikipedia , traiter la non-terminaison est un problème, car l'ajouter produit des logiques incohérentes ( par exemple , je peux définir
wrong :: a -> b
parwrong x = wrong x
, et donc «prouver» que a → b pour tout a et b ). Si c'est ce que vous entendez par «absurdité», alors vous avez tout à fait raison. Si à la place vous vouliez dire la fausse déclaration, alors ce que vous voulez à la place est n'importe quel type inhabité, par exemple quelque chose défini pardata ⊥
- c'est-à-dire un type de données sans aucun moyen de le construire. Cela garantit qu'il n'a aucune valeur du tout et qu'il doit donc être inhabité, ce qui équivaut à false. Je pense que vous pourriez probablement également l'utilisera -> b
, car si nous interdisons les fonctions sans terminaison, cela est également inhabité, mais je ne suis pas sûr à 100%.Wikipedia dit que les axiomes sont codés de deux manières différentes, selon la façon dont vous interprétez Curry-Howard: soit dans les combinateurs, soit dans les variables. Je pense que la vue combinateur signifie que les fonctions primitives qui nous sont données codent les choses que nous pouvons dire par défaut (de la même manière que modus ponens est un axiome parce que l'application de la fonction est primitive). Et je pense que la vue variable peut en fait signifier la même chose - les combinateurs, après tout, ne sont que des variables globales qui sont des fonctions particulières. Quant aux types primitifs: si j'y pense correctement, alors je pense que les types primitifs sont les entités - les objets primitifs sur lesquels nous essayons de prouver les choses.
D'après ma classe de logique et de sémantique, le fait que ( a ∧ b ) → c ≡ a → ( b → c ) (et aussi que b → ( a → c )) est appelé loi d'équivalence d'exportation, du moins en déduction naturelle preuves. Je n'ai pas remarqué à l'époque que c'était juste du curry - j'aurais aimé l'avoir, parce que c'est cool!
Bien que nous ayons maintenant un moyen de représenter la disjonction inclusive , nous n'avons pas de moyen de représenter la variété exclusive. On devrait pouvoir utiliser la définition de la disjonction exclusive pour la représenter: a ⊕ b ≡ ( a ∨ b ) ∧ ¬ ( a ∧ b ). Je ne sais pas comment écrire la négation, mais je sais que ¬ p ≡ p → ⊥, et l'implication et le mensonge sont faciles. On devrait ainsi pouvoir représenter la disjonction exclusive par:
Cela définit
⊥
être le type vide sans valeurs, ce qui correspond à la fausseté;Xor
est alors défini pour contenir à la fois ( et )Either
un a ou un b ( ou ) et une fonction ( implication ) de (a, b) ( et ) au type inférieur ( faux ).Cependant, je n'ai aucune idée de ce que cela signifie .( Edit 1: Maintenant je le fais, voir le paragraphe suivant!) ( Edit 1: Oui, camccann .)Puisqu'il n'y a pas de valeurs de type(a,b) -> ⊥
(y a-t-il?), Je ne peux pas comprendre ce que cela signifierait dans un programme. Quelqu'un connaît-il une meilleure façon de penser à cette définition ou à une autre?Edit 1: Grâce à la réponse de camccann (plus particulièrement, les commentaires qu'il a laissés dessus pour m'aider), je pense voir ce qui se passe ici. Pour construire une valeur de type
Xor a b
, vous devez fournir deux choses. Premièrement, un témoin de l'existence d'un élément de l'una
oub
de l' autre ou comme premier argument; c'est-à-dire aLeft a
ou aRight b
. Et deuxièmement, une preuve qu'il n'y a pas d'éléments des deux typesa
etb
- en d'autres termes, une preuve(a,b)
inhabitée - comme deuxième argument. Puisque vous ne pourrez écrire une fonction qu'à partir de(a,b) -> ⊥
if(a,b)
est inhabité, qu'est-ce que cela signifie pour que ce soit le cas? Cela signifierait qu'une partie d'un objet de type(a,b)
ne peut pas être construit; en d'autres termes, qu'au moins un, et peut-être les deux,a
etb
sont inhabitée aussi bien! Dans ce cas, si nous pensons à la correspondance de motifs, vous ne pourriez pas éventuellement correspondre à des motifs sur un tel tuple: en supposant que ceb
soit inhabité, qu'écririons-nous qui pourrait correspondre à la deuxième partie de ce tuple? Ainsi, nous ne pouvons pas faire de correspondance de motif avec lui, ce qui peut vous aider à comprendre pourquoi cela le rend inhabité. Maintenant, le seul moyen d'avoir une fonction totale qui ne prend aucun argument (comme celui-ci doit, car il(a,b)
est inhabité) est que le résultat soit également d'un type inhabité - si nous y réfléchissons du point de vue de la correspondance de motifs, cela signifie que même si la fonction n'a pas de cas, il n'y a pas de corps possible il pourrait avoir l'un ou l'autre, et donc tout va bien.Une grande partie de cela me fait penser à haute voix / prouver (espérons-le) des choses à la volée, mais j'espère que c'est utile. Je recommande vraiment l'article Wikipédia ; Je ne l'ai pas lu en détail, mais ses tableaux sont un très bon résumé et il est très complet.
la source
Either a a
ne devrait pas tout à fait être un théorème:Either ⊥ ⊥
est toujours inhabité. Tom: Comme l'a dit camccann, la cohérence implique la résiliation. Ainsi, un système de types cohérent ne vous permettra pas d'exprimerf :: a -> b
, et donc le type serait inhabité; un système de type incohérent aurait un habitant pour le type, mais un qui ne se terminerait pas. camccann: Y a-t-il des systèmes de types incohérents qui ne sont pas complets de Turing, occupant un point intermédiaire sur la hiérarchie? Ou est-ce que cette dernière étape (ajouter une récursion générale ou autre) équivaut précisément à l'incohérence?En voici une un peu obscure dont je suis surpris de ne pas avoir été évoquée plus tôt: la programmation réactive fonctionnelle "classique" correspond à la logique temporelle.
Bien sûr, à moins que vous ne soyez un philosophe, mathématicien ou programmeur fonctionnel obsessionnel, cela soulève probablement plusieurs autres questions.
Alors, tout d'abord: qu'est-ce que la programmation réactive fonctionnelle? C'est une façon déclarative de travailler avec des valeurs variant dans le temps . Ceci est utile pour écrire des éléments tels que les interfaces utilisateur, car les entrées de l'utilisateur sont des valeurs qui varient dans le temps. Le FRP "classique" a deux types de données de base: les événements et les comportements.
Les événements représentent des valeurs qui n'existent qu'à des moments discrets. Les frappes au clavier sont un bon exemple: vous pouvez considérer les entrées du clavier comme un caractère à un moment donné. Chaque pression de touche est alors juste une paire avec le caractère de la touche et l'heure à laquelle elle a été enfoncée.
Les comportements sont des valeurs qui existent constamment mais qui peuvent changer continuellement. La position de la souris est un excellent exemple: c'est juste un comportement des coordonnées x, y. Après tout, la souris a toujours une position et, conceptuellement, cette position change continuellement lorsque vous déplacez la souris. Après tout, déplacer la souris est une simple action prolongée, pas un tas d'étapes discrètes.
Et qu'est-ce que la logique temporelle? À juste titre, il s'agit d'un ensemble de règles logiques pour traiter des propositions quantifiées au fil du temps. Essentiellement, il étend la logique normale du premier ordre avec deux quantificateurs: □ et ◇. Le premier signifie "toujours": lisez □ φ comme "φ tient toujours". Le second est "éventuellement": ◇ φ signifie que "φ finira par tenir". Il s'agit d'un type particulier de logique modale . Les deux lois suivantes relient les quantificateurs:
Donc □ et ◇ sont duels l'un à l'autre de la même manière que ∀ et ∃.
Ces deux quantificateurs correspondent aux deux types en FRP. En particulier, □ correspond à des comportements et ◇ correspond à des événements. Si nous réfléchissons à la façon dont ces types sont habités, cela devrait avoir du sens: un comportement est habité à chaque instant possible, alors qu'un événement ne se produit qu'une seule fois.
la source
Lié à la relation entre continuations et double négation, le type d'appel / cc est la loi de Peirce http://en.wikipedia.org/wiki/Call-with-current-continuation
CH est généralement indiqué comme une correspondance entre la logique intuitionniste et les programmes. Cependant si on ajoute l'opérateur call-with-current-continuation (callCC) (dont le type correspond à la loi de Peirce), on obtient une correspondance entre la logique classique et les programmes avec callCC.
la source
Bien que ce ne soit pas un simple isomorphisme, cette discussion sur le LEM constructif est un résultat très intéressant. En particulier, dans la section des conclusions, Oleg Kannedov explique comment l'utilisation de monades pour obtenir l'élimination de la double négation dans une logique constructive est analogue à la distinction des propositions calculables par calcul (pour lesquelles LEM est valide dans un cadre constructif) de toutes les propositions. L'idée que les monades capturent les effets de calcul est ancienne, mais cette instance de l'isomorphisme de Curry-Howard aide à la mettre en perspective et à comprendre ce que la double négation "signifie" réellement.
la source
Le support des continuations de première classe vous permet d'exprimer $ P \ lor \ neg P $. L'astuce est basée sur le fait que ne pas appeler la continuation et sortir avec une expression équivaut à appeler la continuation avec cette même expression.
Pour une vue plus détaillée, veuillez consulter: http://www.cs.cmu.edu/~rwh/courses/logic/www-old/handouts/callcc.pdf
la source
Une chose qui est importante, mais qui n'a pas encore été étudiée, est la relation entre la 2 continuation (continuations qui prend 2 paramètres) et l' AVC de Sheffer . Dans la logique classique, Sheffer stroke peut former un système logique complet par lui-même (plus certains concepts non-opérateurs). Ce qui signifie que le familier
and
,or
,not
peut être mis en œuvre en utilisant uniquement le stoke Sheffer ounand
.C'est un fait important de sa correspondance de type de programmation car il indique qu'un seul combinateur de type peut être utilisé pour former tous les autres types.
La signature de type d'une 2-continuation est
(a,b) -> Void
. Par cette implémentation, nous pouvons définir 1-continuation (continuations normales) comme(a,a)
-> Void, type de produit comme((a,b)->Void,(a,b)->Void)->Void
, type de somme comme((a,a)->Void,(b,b)->Void)->Void
. Cela nous donne une impression impressionnante de sa puissance d'expressivité.Si nous creusons plus loin, nous découvrirons que le graphe existentiel de Piece est équivalent à un langage dont le seul type de données est n-continuation, mais je n'ai vu aucun langage existant sous cette forme. Donc, inventer un pourrait être intéressant, je pense.
la source