Unter der Curry-Howard-Korrespondenz oder locker "Beweise-als-Programme", haben wir auch "Programme-als-Beweise" und was würde einige Arb. Programm beweisen?

Curry-Howard-Korrespondenz

Wählen Sie nun einen beliebigen Algorithmus mit 5 bis 30 Zeilen in einer Programmiersprache Ihrer Wahl aus.

Was beweist das Programm? Oder haben wir nicht auch "Programme als Beweise"?

Nehmen Sie den in Pseudocode geschriebenen GCD-Algorithmus:

function gcd(a, b)
    while b ≠ 0
        t := b
        b := a mod b
        a := t
    return a

Was beweist es? Ich entschuldige mich für die Breite und Weichheit dieser Frage, aber ich wundere mich wirklich darüber.

Ich denke darüber im Kontext der Typentheorie nach, in diesem Fall ist ein Programm ein Beweis dafür, dass sein Typ bewohnt ist.
@Ian eindeutig gibt es etwas, was wir alle vermissen. Der Inhalt der obigen Funktion kann nicht einfach nichts bedeuten ... es muss etwas geben, was sie beweist. Das ist die Frage, die ich nicht herausfinden kann. Bitte arbeiten Sie außerhalb der Typentheorie, wenn dies erforderlich ist. Ich habe das nur deshalb markiert, weil es sehr verwandt erscheint. Sie wählen eine Sache von vielen möglichen Dingen aus, die es beweist. Ja, es beweist vielleicht seinen Prototyp. Aber was beweist es noch? Der Grund, warum wir es nicht beantworten können, ist, dass noch keine solche Zuordnung oder Mathematik entwickelt wurde, zum Beispiel für etwas Gemeinsames wie C ++ oder Python-Sprachen.
Jeder, der hier antwortet oder kommentiert, scheint in der Welt der Typentheorie festzustecken. Ich arbeite in der mathematischen Welt, wo wir Dinge möglich machen können, die vorher nicht bekannt waren.
Die Typentheorie ist Teil der Mathematik. Ihr Programm hat keinen expliziten Typ. Sie könnten es als eine Funktion des Typs anzeigen N × N N , aber das ist ziemlich langweilig und es gibt einfachere Programme dieser Art. Ich denke jedoch, dass es zu weit hergeholt wäre, Ihrem Programm einen komplizierteren Typ zu geben, wie beispielsweise A : N B : N C : N ( ( C | A ) × ( C | B ) × D : N ( ( D | A ) × ( D | B ) ( D | C ) ) ) . In der Praxis verwendete Programmiersprachen ermöglichen solche Beweise meist nicht.
@ZhenLin, dass ein Programm keine Beweise erleichtert, ist nur ein Sprachübersetzungsproblem. Dies ist in der Welt der Sprachen üblich, es müssen komplexe Übersetzer zwischen den beiden beteiligten Sprachen geschrieben werden. Das Programm muss also über ein anderes Programm in einen „menschenlesbaren Proof“ übersetzt werden.
Nein, es ist kein Übersetzungsproblem. Ihr Programm beweist zu keinem Zeitpunkt, dass es einen gcd berechnet hat. Es beweist nicht einmal, dass es einen gemeinsamen Teiler berechnet hat oder dass die Zahl, die es zurückgibt, nicht größer ist als seine Eingaben, oder ...
@ZhenLin du sagst das einfach ohne Beweis. Natürlich habe ich Recht, dass es viele Bemühungen gibt, obwohl die Umstellung von Lean3 auf Lean4 eine riesige Aufgabe ist. Es beweist uns noch nicht mehr, als uns als Neuling Lean4-Code etwas beweist. Sie müssen den Übersetzer in Ihrem Gehirn oder mit einem Computerprogramm erstellen. Der Übersetzungsalgorithmus ist noch unbekannt und wird bei dieser Geschwindigkeit wahrscheinlich nie bekannt sein, da die akademische Gemeinschaft offenbar starr ist, sich nicht an neue Ideen anzupassen.
Ich kenne mich mit computergestützten Proofs aus. Ich habe selbst einige Beweise in Coq geschrieben. Ihr Programm sieht nicht nach einem Beweis aus.
@ZhenLin Coq Beweise sehen für mich nicht wie Beweise aus ... weil ich die Sprache nicht kenne !! Es gibt Bemühungen, Lean3 in englisch lesbare Proofs umzuwandeln. Was der Inhalt der Funktion aussagt, habe ich noch nicht ganz herausgefunden, aber ich stimme nicht zu, dass es angebracht ist, diese Frage einfach "unter den Teppich zu kehren".
Ja, Coq-Beweise sehen nicht wie mathematische Beweise aus. Das sage ich nicht. Ihr Programm sieht nicht einmal wie ein Coq-Beweis der Formel aus, die ich geschrieben habe. Es wird zu viel weggelassen! Ein Typprüfer, der überprüfen könnte, ob Ihr Programm den von mir geschriebenen Typ hat, wäre ein sehr leistungsfähiger automatischer Theorembeweiser.
Eine Antwort, die nicht auf der Curry-Howard-Korrespondenz basiert, findet sich in einem technischen Bericht „Proof by Programming“, der hier zu finden ist: engr.mun.ca/~theo/Publications/proofgramming.pdf

Antworten (3)

Ja. Aber in der Regel sind die dazugehörigen Beweise uninteressant.

Denken Sie daran, dass wir unter der Korrespondenz haben

  • Typen Vorschläge
  • Programme Beweise

Schauen wir uns also Ihren Beispielcode an. Wir bekommen eine Funktion G C D : N × N N . Mit eben diesem Typ beweist sich das Programm

„Wenn es ein Element von gibt N × N , dann gibt es ein Element von N "

ein völlig uninteressantes Theorem, ich denke, Sie würden mir zustimmen. Vor allem die einfache Funktion π 2 ( X , j ) = j , die auch einen Typ hat N × N N beweist denselben Satz. Warum? Weil es sich um zwei Funktionen des gleichen Typs handelt!

Nun, offensichtlich der Code für G C D etwas Interessantes tut, also erweist es sich sicherlich als etwas Interessanteres als das Naive π 2 Funktion. Die Antwort auf diese Frage hängt davon ab, wie ausdrucksstark Ihr Typsystem ist. Wenn Sie nur Zugriff auf einen Typ haben N , dann hast du leider Pech. Aber zum Glück können wir durch die Sprache der abhängigen Typen über interessantere Typen und damit interessantere Aussagen sprechen.


In der Welt der Softwareentwicklung verwenden wir Typen, um das gewünschte Verhalten eines Programms auszudrücken . Typen entsprechen Spezifikationen für Programme.

Auf der einfachsten Ebene sagen sie Ihnen, welche Eingaben und Ausgaben eine bestimmte Funktion erwartet, aber mit abhängigen Typen können wir das gewünschte Verhalten einer Funktion vollständig klassifizieren. Zum Beispiel könnte die naive Art von Mergesort sein

M e R G e S Ö R T : [ A ] [ A ]

alles, was wir sehen, ist, dass es eine Liste von Dingen des Typs enthält A und spuckt eine Liste von Dingen des Typs aus A . Aber auch dies ist keine interessante Spezifikation. Die Identitätsfunktion oder sogar die Funktion, die ihre Eingabe ignoriert und die leere Liste zurückgibt, bewohnen ebenfalls diesen Typ. Und wieder, da Typen Sätze sind, sehen wir das M e R G e S Ö R T beweist eine sehr uninteressante Aussage: "Wenn eine Liste von A s existiert, dann eine Liste von A s existiert .... wie .... ja, offensichtlich.

Also peppen wir unser Typensystem auf und sehen uns stattdessen so etwas an:

M e R G e S Ö R T : [ A ] { L : [ A ] ich S S Ö R T e D ( L ) }

Ich verzichte auf einige Syntax und eine formale Definition von ich S S Ö R T e D : [ A ] B Ö Ö l , aber hoffentlich ist klar, wie man eine solche Funktion schreibt.

Also jetzt M e R G e S Ö R T muss eine strengere Spezifikation erfüllen. Es ist nicht erlaubt, eine Liste von zurückzugeben A S. Es muss eine sortierte Liste von zurückgegeben werden A S. Als Funktion dieses Typs M e R G e S Ö R T beweist

„Wenn eine Liste von A s existiert, dann eine sortierte Liste von A s existiert"

Auch dies ist kein bahnbrechendes Theorem, aber es ist besser als das, was wir zuvor hatten.

Leider entspricht die konstante leere Listenfunktion immer noch dieser Spezifikation. Immerhin ist die leere Liste (leer) sortiert! Aber wir können wieder gehen. Vielleicht schreiben wir so einen Typ M e R G e S Ö R T beweist

„Wenn eine Liste von A s existiert, dann gibt es eine sortierte Liste von A s mit genau den gleichen Elementen"

was als Vorschlag ein bisschen interessanter wird. Dies legt nun das gewünschte Verhalten von Mergesort fest. Überlegen Sie außerdem, wie Sie eine solche Behauptung beweisen würden. Wenn jemand sagte "beweise, dass eine sortierte Liste mit denselben Elementen existiert", würdest du sagen "sortiere sie einfach!" und das ist genau der Beweis dafür M e R G e S Ö R T bietet.


Was dann, von Ihrem G C D Beispiel? Nun, wie bei M e R G e S Ö R T , funktioniert dasselbe Programm, um mehrere Dinge zu beweisen. Es ist wirklich der Typ , der zählt. Mit ein wenig Arbeit könnte Ihr Code ein Beweis dafür sein

„Wenn es zwei natürliche Zahlen gibt, dann gibt es eine natürliche Zahl, die beide teilt, die größer ist als jede andere natürliche Zahl, die beide teilt“

das beginnt, wie ein interessanter (wenn auch grundlegender) mathematischer Satz auszusehen!


Ich hoffe das hilft ^_^

Ich bin auf der Suche nach mehr Positivität darüber, dass ein Programm möglicherweise etwas beweist . Seine Spezifikation ist eine Sprache, die möglicherweise in abhängige Typen übersetzbar ist.
@PenAndPaperMathematics In einer abhängig typisierten Sprache haben Sie normalerweise zusätzliche Informationen (wie im Typ), die die mathematische Definition der GCD enthalten, zusammen mit einem Beweis, dass sie dem Programm entspricht. Aber wie würde die Spezifikation in diesem Fall aussehen?
@PenAndPaperMathematics (Wenn Sie alternativ Eigenschaften von imperativen Programmen beweisen möchten, wenn der Curry-Howard-Isomorphismus nicht direkt anwendbar ist, gibt es Sprachen wie Dafny und Liquid Haskell, mit denen Sie Behauptungen in Ihren Code aufnehmen können, die der Compiler automatisch zu beweisen versucht. )
@PenAndPaperMathematics Möglicherweise können Sie sich Ihre Definition als ein Axiom vorstellen, das besagt, dass "jeder Ausdruck der Form gcd(a, b)gleich ist while b ≠ 0; ...; return a". (Vielleicht ist das aber nicht das, was Sie meinen, da dies keinen direkten Bezug zu den mathematischen Eigenschaften des ggT hat.)
Gehen diese Beweise nicht davon aus, dass das Programm wie mergesortrichtig ist? Klingt nach einem Beweis, der einen Beweis seiner nichttrivialen Annahmen erfordert.
@Ruslan - du hast vollkommen recht. Ich beschönige hier viele Implementierungsdetails. Vor allem der Code für M e R G e S Ö R T Das Bewohnen dieser komplizierteren Typen muss mit vielen ~Bonusinformationen~ dekoriert werden, die beweisen, dass es wirklich hält, was es verspricht. Dies ist einer der Gründe, warum sich Softwareingenieure für abhängige Typen interessieren: Es verwirklicht wirklich den Traum der funktionalen Programmierung, "wenn es kompiliert, ist es korrekt". Der Nachteil dieser zusätzlichen statischen Leistung besteht darin, dass der Programmierer im Grunde nur Notizen schreiben muss M e R G e S Ö R T , sondern auch ein Korrektheitsbeweis!
Um es nur zu betonen, die Typen sind wichtig , um festzustellen, was Sie bewiesen haben. Wenn die obige gcd-Funktion in Python ohne Typsignaturen geschrieben ist, dann beweist sie "wenn ich zwei Dinge habe, dann gibt es mindestens eine Sache im Universum", was noch wertloser ist (und/oder unglaublich tief, je nachdem ob du Mathe oder Philosophie studierst)

Die Curry-Howard-Korrespondenz kann sowohl als „Beweise-als-Programme“ als auch als „Programme-als-Beweise“ angesehen werden, vorausgesetzt, wir spezifizieren, was die Logik für Beweise und die Sprache für Programme sind.

Die ursprüngliche Formulierung der Curry-Howard-Korrespondenz besteht zwischen Beweisen in einer bestimmten Logik (das implikationale Fragment der intuitionistischen Logik, auch bekannt als minimale Logik) und Programmen in einer bestimmten Sprache (einfach getippt λ -Rechnung ). Später wurde die Korrespondenz auf andere Logiken und Sprachen ausgedehnt (z. B. Minimallogik mit Implikation und Konjunktion und einfach getippt λ -Rechnen mit Paaren; Klassische Logik u λ μ -Rechnung ; usw.).

Ein entscheidender Punkt ist, dass die "Programmiersprache" in keiner Version der Curry-Howard-Korrespondenz Turing-vollständig sein kann . Tatsächlich ist die Logik, in der Beweise geschrieben werden, kohärent, und in der Beweistheorie bedeutet dies, dass ein Theorem namens Cut-Elimination oder Normalisierung gilt, das impliziert, dass jedes in der entsprechenden "Programmiersprache" geschriebene Programm seine Ausführung beenden muss. Nun ist bekannt, dass eine Sprache, die es uns nicht erlaubt, ein Programm zu schreiben, das sich endlos wiederholt, nicht Turing-vollständig ist.

Um Ihre spezielle Frage zu beantworten, das von Ihnen geschriebene Programm enthält das whileKonstrukt, das Endlosschleifen erzeugen kann, daher kann es keine logische Interpretation im Sinne von Curry-Howard haben, dh es entspricht keinem Beweis in irgendeiner Logik.

Es stimmt, dass es einige Problemumgehungen für diese Einschränkung gibt. Als Programmiersprache können wir zum Beispiel einfach typisiert betrachten λ -Kalkül mit einem Fixpunkt-Kombinator, der Turing-vollständig ist (siehe auch eine Diskussion hier ). Bei dieser Variante wird der einfach getippt λ -Kalkül whilekann das Konstrukt kodiert werden. Aber was ist der logische Inhalt einer solchen Programmiersprache aus der Curry-Howard-Perspektive? Nichts, denn der Fixpunkt-Kombinator würde einem Axiom in unserem logischen System entsprechen, das den Cut-Elimination-Theorem bricht.

Grob gesagt repräsentiert in der Beweistheorie ein Beweissystem ohne Schnitteliminationssatz eine inkohärente Logik, also eine "Nicht-Logik". Anders gesagt, Curry-Howard zeigt, dass es eine unüberbrückbare Spannung zwischen zwei wichtigen Aspekten gibt (siehe auch hier ): Kohärenz (in der Logik) und Turing-Vollständigkeit (in Programmiersprachen). Das eine schließt das andere aus, aber eine Logik ohne Kohärenz ist keine Logik, und eine Programmiersprache, die nicht Turing-vollständig ist, garantiert nicht, dass Sie das Programm schreiben können, das Sie schreiben möchten (aber es gibt „Programmiersprachen“ wie z System F , die auf Curry-Howard-Korrespondenz basieren und sehr ausdrucksstark sind, obwohl sie nicht Turing-vollständig sind).

Selbstverständlich sind in der Praxis verwendete Programmiersprachen wie C, Python, C++, Java, OCaml etc. Turing-vollständig und benutzerfreundlich konzipiert, also für beliebige Programmtypen A B in einer dieser Sprachen geschrieben ist, ist es von Natur aus unmöglich, einen logischen Beweis dafür zu extrahieren A B in jedem Beweissystem, es sei denn, das Programm verwendet sehr grundlegende Konstrukte, die in eine der "Programmiersprachen" übersetzt werden können, für die die Curry-Howard-Korrespondenz gilt (leider ist dies ziemlich selten, zum Beispiel ist es nicht der Fall, wenn das Programm enthält eine whileSchleife).

Warum können wir die logische Seite nicht so erweitern, dass eine Turing-vollständige Sprache tatsächlich einer Sprache zum Beweisen entspricht?
(+1) für die Bemerkung, dass eine solche Programmiersprache formal nicht vollständig sein kann. Ich habe eine lebhafte (wenn auch etwas peinliche) Erinnerung daran, als ich zum ersten Mal erfuhr, dass die im HoTT-Buch verwendete Programmiersprache nicht vollständig sein kann.
@PenAndPaperMathematics - Ich habe den letzten Teil meiner Antwort bearbeitet, um Ihre Frage zu beantworten.
Die Frage, welche Funktionen Programme, die in gängigen Programmiersprachen geschrieben sind, tatsächlich berechnen, da sie möglicherweise nicht beendet werden (oder schlimmer noch, Nebeneffekte haben!), ist ziemlich knifflig. Es gibt eine sogenannte Denotationssemantik , die versucht, über die naive Interpretation von Typen als gewöhnliche Mengen hinauszugehen, um auch nicht terminierenden Programmen eine nicht-triviale Bedeutung zu verleihen.
Natürlich sind Sprachen wie die in Coq oder HoTT verwendeten Sprachen sehr nahe daran, Turing vollständig zu sein, in dem Sinne, dass es ziemlich einfach ist, eine Funktion zu schreiben, die eine Turing-Maschinenspezifikation, einen anfänglichen Bandstatus und eine Ganzzahl enthält N Läuft die Turing-Maschine für bis zu N Schritte. (Oder Sie könnten zumindest in Coq auch eine Funktion schreiben, die eine Turing-Maschine und einen Anfangszustand zusammen mit einem Beweis irgendeiner Art annimmt, dass die Ausführung der Turing-Maschine anhält.)
@PenAndPaperMathematics: Es kann hilfreich sein, Currys Paradoxon zu überprüfen , das das "Problem" ist, das Sie bekommen, wenn Sie versuchen, die CH-Korrespondenz auf Turing-vollständige Programmiersprachen anzuwenden. Kurz gesagt, sie lassen Sie Dinge "beweisen", die eigentlich nicht wahr sind.
Darf ich nach dem Grund für die Ablehnung fragen?

Lassen M sei der gemessene Speicherplatz einer Maschine, auf der ein in Sprache geschriebenes Programm läuft L , M kann sein Z / 2 32 oder eine Norm int Datentyp auf vielen Maschinen. Oder es könnte sein ich = 1 N ( Z / 2 32 ) für einige N 1 .

Dies ist ein gutes Maß dafür, was das Programm tut: Das Programm wirkt auf die Register, den Cache, den Arbeitsspeicher und die Speicherung einer Maschine, und das deckt so ziemlich alles ab. Um an einen Port zu schreiben, würden Sie einen der oben genannten verwenden, z

Also dann ein Programm, das den Datentyp nimmt M als Eingabe und Verdopplung (modulo 2 32 ) sieht in Python so aus:

(Nehmen wir an, Python hat int32 und keine Ganzzahlen mit beliebiger Genauigkeit, was standardmäßig in der Standardversion von CPython der Fall ist.)

a = int(input("a="))
a = a << 1

Rufen Sie mit an python double_int.py 2. Ich weiß, dass es keine Ausgabe an das Programm gibt, aber dies ist für ein theoretisches Argument. Also ein Programm F nimmt eine Maschine in gemessenen Zustand M M und mutiert die Maschine so, dass sie sich im gemessenen Zustand befindet F ( M ) M .

Ich möchte sagen, dass ein Programm eine Implementierung eines Algorithmus darstellt A so dass, wenn Sie das Programm ausführen F auf seinem (notwendigerweise begrenzten, aber manchmal riesigen) Speicherplatz M ), dh Sie rechnen F ( M ) für alle M M und das Ergebnis manuell überprüfen oder das Ergebnis mit dem Ergebnis eines vertrauenswürdigen anderen Programms vergleichen (Einheitentest) und das Ergebnis auscheckt, dann ist das Programm ein Beweis dafür A ist in der Sprache realisierbar L , und das A ist in der Tat ein gültiger Algorithmus, der berechnet, was er sagt.

Also wann | M | <≈ 10 9 und es läuft ein Protokoll ( M ) Zeit oder weniger, kann man innerhalb weniger Sekunden bis zu einer Stunde einen "Proof of Algorithmus" überprüfen A „Benutzung der Maschine M , und das Programm F .

Was meinst du mit "Beweis des Algorithmus A "?
@RuggieroRilievi es wird grob nicht formal im Absatz direkt vor der letzten Aussage beschrieben. Ich denke, Ideen beginnen informell in unseren Köpfen, später wandeln wir sie in formale Definitionen um.
Ein Programm F ist, wie ich beschrieben habe, ein Beweis (nachdem Sie es viele Male für alle Eingaben ausgeführt haben - notwendigerweise in endlicher Zeit überprüfbar, da moderne (reale Maschinen) von Natur aus endliches Gedächtnis haben), dass ein Algorithmus ein Algorithmus ist A gültig ist (dass es tatsächlich ein gegebenes Problem löst) und dass es als implementiert wird F in der Sprache L , auf Maschine M . Außerdem ist es ein Beweis dafür, dass das gegebene Problem berechenbar ist!
Ich denke, das Programm selbst stellt keinen Beweis dar (nach üblicher Interpretation des Wortes), aber eine vollständige Aufzeichnung des Validierungsprozesses tut es. Abgesehen davon ist jede Funktion über eine endliche Domäne als Case-Anweisung implementierbar und daher in so ziemlich jeder Sprache oder jedem Berechnungsmodell (z. B. endliche Zustandsmaschinen) berechenbar und implementierbar.