Wie kann man die Gleichheit der Typen ohne Klassen induktiv beweisen?

8

Ich versuche, die Assoziativität von Listen auf Typebene so zu beweisen, dass ich zwischen äquivalenten Typen konvertieren kann, ohne Einschränkungen mitzunehmen.

Angenommen, die Standarddefinition der Verkettung:

type family (++) (xs :: [k]) (ys :: [k]) :: [k] where
  '[] ++ ys = ys
  (x ': xs) ++ ys = x ': (xs ++ ys)

Angenommen, ich habe eine Funktion:

given :: forall k (a :: [k]) (b :: [k]) (c :: [k]). Proxy ((a ++ b) ++ c)
given = Proxy  -- Proxy is just an example

und ich möchte diese Funktion aufrufen und dann Assoziativität verwenden:

my :: forall k (a :: [k]) (b :: [k]) (c :: [k]). Proxy (a ++ (b ++ c))
my = given @k @a @b @c  -- Couldn't match type ‘(a ++ b) ++ c’ with ‘a ++ (b ++ c)’

Diese Typgleichheit ist in der Tat nicht trivial, daher ist es keine Überraschung, dass der Compiler sie nicht versteht, aber ich kann es beweisen! Leider weiß ich nicht, wie ich den Compiler davon überzeugen kann, dass ich es kann.

Mein natürlicher erster Gedanke ist, etwas zu tun wie:

proof :: forall k (a :: [k]) (b :: [k]) (c :: [k]). (a ++ (b ++ c)) :~: ((a ++ b) ++ c)
proof = _

und dann ändere meine Funktion in:

my :: forall k (a :: [k]) (b :: [k]) (c :: [k]). Proxy (a ++ (b ++ c))
my = case proof @k @a @b @c of Refl -> given @k @a @b @c

Aber ich muss noch definieren proofund dafür muss ich eine Induktion für seine Typargumente durchführen. Die einzige Möglichkeit, Induktionen für Typen in Haskell durchzuführen, die ich kenne, besteht darin, eine Typklasse zu definieren, aber dann muss ich dem Typ von die entsprechende Einschränkung hinzufügen my, die ich nicht tun möchte - die Tatsache, dass sie aufgerufen wird givenund erzwingt, dass das Ergebnis ein „Implementierungsdetail“ ist.

Gibt es eine Möglichkeit, diese Art der Typengleichheit in Haskell zu beweisen, ohne auf unsichere Postulate zurückzugreifen?

Kirelagin
quelle
1
Dies wäre ein Anwendungsfall für abhängige Typen, aber Haskell hat diese nicht. Wir müssen auf Singletons zurückgreifen (damit wir Muster abgleichen und wiederkehren können), möglicherweise durch Typklassen. Ich glaube nicht, dass Sie einen Nicht-Bottom-Term vom Typ (a++(b++c)) :~: ((a++b)++c)ohne weitere Singleton-Argumente oder Typklasseneinschränkungen schreiben können .
Chi

Antworten:

6

Nein, Sie können dies nicht ohne eine Einschränkung der Typklasse beweisen, da dies nicht der Fall ist. Hier ist insbesondere ein Gegenbeispiel:

Any ++ ([] ++ []) -- reduces to Any ++ []
(Any ++ []) ++ [] -- does not reduce

Um die (dumme) Existenz von auszuschließen Any, müssen Sie eine Typklasse verwenden, die keine AnyInstanz hat. keine andere Wahl.

Daniel Wagner
quelle
Ah, richtig, ich vergesse immer wieder Any:(. Ich hatte gehofft, dass die freundlichen Anmerkungen in meiner Definition von ++garantieren würden, dass es wahr ist, aber dies wird Anydeutlich
gebrochen
1
Ist im AnyGrunde nicht nur undefinedaber auf Typebene? Da es moralisch korrekt ist, so zu tun, als gäbe es das letztere nicht, warum können wir das nicht auch für das erstere tun?
Joseph Sible-Reinstate Monica
4
@ JosephSible-ReinstateMonica Natürlich können wir so tun, als ob Sie möchten. Und dann ist die Art und Weise, wie Sie Ihren Anspruch auf Typebene in die Berechnungsebene konvertieren, zu verwenden unsafeCoerce. Aber die Frage fordert ausdrücklich zu vermeiden unsafeCoerce, und so können wir doch nicht so tun.
Daniel Wagner