Warum ist eine Kontextreduktion notwendig?

8

Ich habe gerade dieses Papier gelesen ("Type classes: a exploration of der Design Space "von Peyton Jones & Jones, der einige Herausforderungen mit dem frühen Typenklassensystem von Haskell erklärt, und wie man es verbessern kann.

Viele der von ihnen angesprochenen Probleme beziehen sich auf die Kontextreduzierung , mit der die Beschränkungen gegenüber Instanz- und Funktionsdeklarationen reduziert werden können, indem die "umgekehrte Folge" -Beziehung eingehalten wird.

z.B. Wenn Sie irgendwo instance (Ord a, Ord b) => Ord (a, b) ... haben, dann wird Ord (a, b) innerhalb von Kontexten auf {Ord a, Ord b} reduziert (Reduktion reduziert nicht immer die Anzahl der Beschränkungen).

Ich habe aus der Zeitung nicht verstanden, warum diese Reduzierung notwendig war.

Nun, ich habe festgestellt, dass es benutzt wurde, um irgendeine Form der Typprüfung durchzuführen. Wenn Sie Ihre eingeschränkte Gruppe von Bedingungen haben, können Sie prüfen, ob eine Instanz vorhanden ist, die sie erfüllen kann, andernfalls ist es ein Fehler. Ich bin mir nicht sicher, was der Mehrwert davon ist, da Sie das Problem an der Verwendungsstelle bemerken würden, aber okay.

Aber selbst wenn Sie diese Überprüfung durchführen müssen, warum sollten Sie das Ergebnis der Reduktion in abgeleiteten Typen verwenden? Das Papier weist darauf hin, dass es zu nicht intuitiven abgeleiteten Typen führt.

Das Papier ist ziemlich alt (1997), aber soweit ich das beurteilen kann, ist die Reduzierung des Kontexts immer noch ein fortwährendes Problem. Die Spezifikation Haskell 2010 erwähnt das Inferenzverhalten, das ich oben erklärt habe ( link ).

Also, warum geht das so?

    
Norswap 01.02.2016, 18:27
quelle

2 Antworten

9
Ich weiß nicht, ob das der Grund ist, notwendigerweise, aber es könnte als ein Grund betrachtet werden: in frühen Haskell, Typ-Signaturen durften nur "einfache" Einschränkungen haben, nämlich einen Typ-Klassennamen, der auf einen Typ angewendet wurde Variable. So zum Beispiel waren alle diese in Ordnung:

%Vor%

Aber keiner von diesen:

%Vor%

Ich denke, es gab damals - und auch heute noch - das Gefühl, dass es dem Compiler leichtfallen würde, auf einen Typ zu schließen, den man nicht manuell schreiben könnte. Die Kontextreduzierung war eine Möglichkeit, die obigen Signaturen entweder in solche zu verwandeln, die von Hand geschrieben werden konnten, oder einen informativen Fehler. Zum Beispiel, da man vernünftigerweise

haben könnte %Vor%

Im Geltungsbereich könnten wir die illegale Signatur Ord (Tree a) => ... in die rechtliche Signatur Ord a => ... umwandeln. Auf der anderen Seite, wenn wir keine Instanz von Eq für Funktionen im Bereich haben, würde man einen Fehler über den Typ melden, von dem abgeleitet wurde, dass Eq (a -> b) in seinem Kontext benötigt wird.

Das hat noch ein paar andere Vorteile:

  1. Intuitiv gefällig. Viele der Kontextreduzierungsregeln ändern nicht, ob der Typ legal ist, sondern reflektieren Dinge, die Menschen tun würden, wenn sie den Typ schreiben. Ich denke hier an die Regeln für die Deduplizierung und Subsumtion, mit denen Sie sich z. (Eq a, Eq a, Ord a) in nur Ord a - eine Transformation, die man unbedingt für die Lesbarkeit machen möchte.
  2. Dies kann häufig dumme Fehler auffangen; Anstatt einen Typ wie Eq (Integer -> Integer) => Bool abzuleiten, der nicht gesetzmäßig erfüllt werden kann, kann man einen Fehler wie Perhaps you did not apply a function to enough arguments? melden. Viel freundlicher!
  3. Es wird Aufgabe des Compilers herauszufinden, was falsch gelaufen ist. Anstatt einen komplizierten Kontext wie Eq (Tree (Grizwump a, [Flagle (Gr n e) (Gr n' e') c])) zu folgern und zu beschweren, dass der Kontext nicht erfüllbar ist, ist er stattdessen gezwungen, dies auf die konstituierenden Beschränkungen zu reduzieren; Es wird sich stattdessen beschweren, dass wir Eq (Grizwump a) aus dem bestehenden Kontext nicht bestimmen konnten - ein viel präziser und umsetzbarer Fehler.
Daniel Wagner 01.02.2016 20:12
quelle
6

Ich denke, das ist in einer Wörterbuchübergabe-Implementierung tatsächlich wünschenswert. In einer solchen Implementierung wird ein "Dictionary", dh ein Tupel oder ein Satz von Funktionen, als implizites Argument für jede Typklassenbeschränkung im Typ der angewendeten Funktion übergeben.

Nun ist die Frage einfach, wann und wie diese Wörterbücher erstellt werden. Beachten Sie, dass für einfache Typen wie Int notwendigerweise alle Wörterbücher für jede Art von Klasse Int eine Instanz von wird eine Konstante sein. Nicht so bei parametrisierten Typen wie Listen, Maybe oder Tupeln. Es ist klar, dass, um beispielsweise ein Tupel anzuzeigen, die Show -Instanzen der tatsächlichen Tupelelemente bekannt sein müssen. Daher kann ein solches polymorphes Wörterbuch keine Konstante sein.

Es scheint, dass das Prinzip, das die Wörterbuchübergabe leitet, so ist, dass nur Wörterbücher für Typen übergeben werden, die als Typvariablen im Typ der angewendeten Funktion erscheinen. Oder, um es anders auszudrücken: Es werden keine redundanten Informationen repliziert.

Betrachten Sie diese Funktion:

%Vor%

Die Information, dass ein Tupel von sichtbaren Komponenten ebenfalls angezeigt werden kann, daher muss eine Einschränkung wie Show (a,b) nicht erscheinen, wenn wir bereits (Show a, Show b) kennen.

Eine alternative Implementierung wäre jedoch möglich, wenn der Aufrufer dafür verantwortlich wäre, Wörterbücher zu erstellen und weiterzuleiten. Dies könnte ohne Kontextreduzierung funktionieren, so dass der Typ von f wie folgt aussehen würde:

%Vor%

Aber das würde bedeuten, dass der Code zum Erstellen des Tupel-Wörterbuchs auf jeder Aufrufseite wiederholt werden müsste. Und es ist leicht, Beispiele zu finden, bei denen die Anzahl notwendiger Einschränkungen tatsächlich zunimmt, wie in:

%Vor%

Es ist lehrreich, ein Typklassen- / Instanzsystem mit tatsächlichen, explizit übergebenen Datensätzen zu implementieren. Zum Beispiel:

%Vor%

Sobald Sie dies tun, werden Sie wahrscheinlich leicht die Notwendigkeit einer "Kontextreduzierung" erkennen.

    
Ingo 01.02.2016 20:16
quelle