Wie vergleiche ich zwei Funktionen auf Äquivalenz wie in (λx.2 * x) == (λx.x + x)?


72

Gibt es eine Möglichkeit, zwei Funktionen auf Gleichheit zu vergleichen? Zum Beispiel (λx.2*x) == (λx.x+x)sollte true zurückgegeben werden, da diese offensichtlich gleichwertig sind.


2
Benötigen Sie wirklich arithmetische Funktionen oder sind Sie nur neugierig auf den Vergleich von Funktionen? Im letzteren Fall werfen Sie einen Blick auf die Normalisierung in typisierten Lambda-Steinen.
Lukstafi

@ Lukstafi nur neugierig, aber ich werde es mir ansehen.
MaiaVictor

7
Ihr Konnektiv "aber" ist fehl am Platz, es sollte eher "so" sein. ;-)
Lukstafi

1
@ Lukstafi du hast recht.
MaiaVictor

1
@IvanCastellanos Das klingt großartig, bis Sie die Gleichwertigkeit von zwei Binärfunktionen beweisen möchten und plötzlich die Domänengröße von 4 Milliarden 16 Billionen betrug und Ihre vorherige 1-Minuten-Testsuite zu einer 10000-Jahres-Testsuite wurde.
Daniel Wagner

Antworten:


124

Es ist ziemlich bekannt, dass die allgemeine Funktionsgleichheit im Allgemeinen nicht zu entscheiden ist. Daher müssen Sie eine Teilmenge des Problems auswählen, an dem Sie interessiert sind. Sie können einige dieser Teillösungen in Betracht ziehen:

  • Presburger-Arithmetik ist ein entscheidbares Fragment von Logik erster Ordnung + Arithmetik.
  • Das Universumspaket bietet Funktionsgleichheitstests für Gesamtfunktionen mit endlicher Domäne.
  • Sie können überprüfen, ob Ihre Funktionen für eine ganze Reihe von Eingaben gleich sind, und dies als Beweis für die Gleichheit bei den nicht getesteten Eingaben behandeln. Schauen Sie sich QuickCheck an .
  • SMT-Löser bemühen sich nach besten Kräften und antworten manchmal mit "Weiß nicht" anstelle von "Gleich" oder "Nicht gleich". Bei Hackage gibt es mehrere Bindungen zu SMT-Lösern. Ich habe nicht genug Erfahrung, um die beste vorzuschlagen, aber Thomas M. DuBuisson schlägt sbv vor .
  • Es gibt eine unterhaltsame Forschungslinie zur Entscheidung über Funktionsgleichheit und andere Dinge zu kompakten Funktionen. Die Grundlagen dieser Forschung werden im Blogbeitrag Scheinbar unmögliche Funktionsprogramme beschrieben . (Beachten Sie, dass Kompaktheit eine sehr starke und sehr subtile Bedingung ist! Sie wird von den meisten Haskell-Funktionen nicht erfüllt.)
  • Wenn Sie wissen, dass Ihre Funktionen linear sind, können Sie eine Basis für den Quellraum finden. dann hat jede Funktion eine eindeutige Matrixdarstellung.
  • Sie könnten versuchen, Ihre eigene Ausdruckssprache zu definieren, zu beweisen, dass die Äquivalenz für diese Sprache entscheidbar ist, und diese Sprache dann in Haskell einbetten. Dies ist der flexibelste, aber auch der schwierigste Weg, um Fortschritte zu erzielen.

8
Sind Sie sicher, dass er nicht nur nach sbv oder quickcheck sucht? Mit SBV: prove $ \(x::SInt32) -> 2*x .== x + xErgebnisse inQ.E.D.
Thomas M. DuBuisson

@ ThomasM.DuBuisson Toller Vorschlag! Ich werde es der Antwort hinzufügen.
Daniel Wagner

Ich suchte tatsächlich nach einem tieferen Überblick über das Problem, genau das, was Daniel zur Verfügung stellte.
MaiaVictor

42

Dies ist im Allgemeinen unentscheidbar, aber für eine geeignete Teilmenge können Sie dies heute tatsächlich effektiv mit SMT-Lösern tun:

$ ghci
GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
Prelude> :m Data.SBV
Prelude Data.SBV> (\x ->  2 * x) === (\x -> x + x :: SInteger)
Q.E.D.
Prelude Data.SBV> (\x ->  2 * x) === (\x -> 1 + x + x :: SInteger)
Falsifiable. Counter-example:
  s0 = 0 :: Integer

Weitere Informationen finden Sie unter: https://hackage.haskell.org/package/sbv


11

Lassen Sie uns zusätzlich zu den praktischen Beispielen in der anderen Antwort die Teilmenge der Funktionen auswählen, die in der typisierten Lambda-Rechnung ausgedrückt werden können. Wir können auch Produkt- und Summentypen zulassen. Obwohl die Überprüfung, ob zwei Funktionen gleich sind, so einfach sein kann, wie sie auf eine Variable anzuwenden und die Ergebnisse zu vergleichen , können wir die Gleichheitsfunktion nicht in der Programmiersprache selbst erstellen .

ETA: λProlog ist eine logische Programmiersprache zum Manipulieren von Funktionen (typisierter Lambda-Kalkül).


1
Sie sagen: "Die Überprüfung, ob zwei Funktionen gleich sind, kann so einfach sein, wie sie auf eine Variable anzuwenden und die Ergebnisse zu vergleichen." Es fällt mir allerdings schwer, das zu glauben. Würde dies als einfaches Beispiel die Gleichheit wirklich bestätigen (\x -> 2*x) == (\x -> x*2)?
Daniel Wagner

"(\ x -> 2 * x) == (\ x -> x * 2)" ist nicht unbedingt wahr, es hängt davon ab, wie Sie "*" und "2" interpretieren. Zum Beispiel könnten Sie "==" auf Grundbegriffen definieren, um ein Modul für das Umschreiben von Begriffen zu sein.
Lukstafi

9

2 Jahre sind vergangen, aber ich möchte dieser Frage eine kleine Bemerkung hinzufügen. Ursprünglich habe ich gefragt, ob es eine Möglichkeit gibt, festzustellen, ob dies (λx.2*x)gleich ist (λx.x+x). Addition und Multiplikation auf dem λ-Kalkül können definiert werden als:

add = (a b c -> (a b (a b c)))
mul = (a b c -> (a (b c)))

Wenn Sie nun die folgenden Begriffe normalisieren:

add_x_x = (λx . (add x x))
mul_x_2 = (mul (λf x . (f (f x)))

Du erhältst:

result = (a b c -> (a b (a b c)))

Für beide Programme. Da ihre normalen Formen gleich sind, sind beide Programme offensichtlich gleich. Während dies im Allgemeinen nicht funktioniert, funktioniert es in der Praxis für viele Begriffe. (λx.(mul 2 (mul 3 x))und (λx.(mul 6 x))beide haben zum Beispiel die gleichen Normalformen.


1
Es gibt eine Technik namens "Supercompilation" (ich empfehle dieses Papier). Ich denke, ein ausgereifter Supercompiler kann Ihre Funktionen vereinheitlichen, selbst wenn sie durch Rekursion und Mustervergleich definiert sind.
user3237465

1
@ user3237465 Der angegebene Link funktioniert nicht mehr. Dieses Forschungspapier ist hier verfügbar: akademia.edu/2718995/Rethinking_supercompilation
Stephane Rolland

4 Jahre sind vergangen, und ich möchte noch eine Bemerkung hinzufügen: Während dies in diesem Fall funktioniert, ist so etwas meistens die Ausnahme. Funktionen können auf sehr unterschiedliche Weise definiert werden und sind dennoch gleichwertig. Daher ist eine Möglichkeit, Gleichungen manuell zu bearbeiten, hilfreich.
MaiaVictor

3

In einer Sprache mit symbolischer Berechnung wie Mathematica:

Geben Sie hier die Bildbeschreibung ein

Oder C # mit einer Computeralgebra-Bibliothek :

MathObject f(MathObject x) => x + x;
MathObject g(MathObject x) => 2 * x;

{
    var x = new Symbol("x");

    Console.WriteLine(f(x) == g(x));
}

Oben wird 'True' an der Konsole angezeigt.


Aber es (x \[Function] x + x) == (y \[Function] 2 y)ist etwas, das es nicht einmal versucht.
tfb

0

Es ist im Allgemeinen unentscheidbar, zwei Funktionen als gleich zu beweisen, aber in besonderen Fällen wie in Ihrer Frage kann man immer noch die funktionale Gleichheit nachweisen.

Hier ist ein Beispielnachweis in Lean

def foo : (λ x, 2 * x) = (λ x, x + x) :=
begin
  apply funext, intro x,
  cases x,
  { refl },
  { simp,
    dsimp [has_mul.mul, nat.mul],
    have zz : ∀ a : nat, 0 + a = a := by simp,
    rw zz }
end

Man kann dasselbe in einer anderen abhängig typisierten Sprache wie Coq, Agda, Idris tun.

Das Obige ist ein taktischer Stilbeweis. Die tatsächliche Definition von foo(dem Beweis), der erzeugt wird, ist ein ziemlicher Bissen, der von Hand geschrieben werden muss:

def foo : (λ (x : ℕ), 2 * x) = λ (x : ℕ), x + x :=
funext
  (λ (x : ℕ),
     nat.cases_on x (eq.refl (2 * 0))
       (λ (a : ℕ),
          eq.mpr
            (id_locked
               ((λ (a a_1 : ℕ) (e_1 : a = a_1) (a_2 a_3 : ℕ) (e_2 : a_2 = a_3), congr (congr_arg eq e_1) e_2)
                  (2 * nat.succ a)
                  (nat.succ a * 2)
                  (mul_comm 2 (nat.succ a))
                  (nat.succ a + nat.succ a)
                  (nat.succ a + nat.succ a)
                  (eq.refl (nat.succ a + nat.succ a))))
            (id_locked
               (eq.mpr
                  (id_locked
                     (eq.rec (eq.refl (0 + nat.succ a + nat.succ a = nat.succ a + nat.succ a))
                        (eq.mpr
                           (id_locked
                              (eq.trans
                                 (forall_congr_eq
                                    (λ (a : ℕ),
                                       eq.trans
                                         ((λ (a a_1 : ℕ) (e_1 : a = a_1) (a_2 a_3 : ℕ) (e_2 : a_2 = a_3),
                                             congr (congr_arg eq e_1) e_2)
                                            (0 + a)
                                            a
                                            (zero_add a)
                                            a
                                            a
                                            (eq.refl a))
                                         (propext (eq_self_iff_true a))))
                                 (propext (implies_true_iff ℕ))))
                           trivial
                           (nat.succ a))))
                  (eq.refl (nat.succ a + nat.succ a))))))
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.