Wiederverwendung / Memoisierung von globalen polymorphen (Klassen-) Werten in Haskell

8

Ich mache mir Gedanken darüber, ob und wann ein polymorpher "globaler" Klassenwert freigegeben / gespeichert wird, insbesondere über Modulgrenzen hinweg. Ich habe dies gelesen und dies , aber sie scheinen nicht ganz meine Situation widerzuspiegeln und ich sehe ein anderes Verhalten als das, was man von den Antworten erwarten könnte.

Betrachten Sie eine Klasse, die einen Wert darstellt, dessen Berechnung teuer sein kann:

%Vor%

Und ein separates Modul:

%Vor%

Das Ausführen von main ergibt zwei separate Bewertungen von costly (wie von der Ablaufverfolgung angezeigt): eins für foo und eins für bar . Ich weiß, dass costlyInt nur die (bewertete) costly von foo zurückgibt, denn wenn ich print foo von main entferne, wird die erste costlyInt teuer. (Ich kann auch costlyInt dazu veranlassen, eine separate Auswertung durchzuführen, indem der Typ von foo auf Num a => a verallgemeinert wird.)

Ich denke, ich weiß, warum dieses Verhalten passiert: Die Instanz von Costly ist im Grunde eine Funktion, die ein Num Wörterbuch verwendet und ein Costly Wörterbuch generiert. Wenn also bar kompiliert wird und der Verweis auf costly aufgelöst wird, erzeugt ghc ein neues Costly Wörterbuch, das einen teuren Thunk enthält. Frage 1: Habe ich das richtig?

Es gibt mehrere Möglichkeiten, um nur eine Auswertung von costly zu erstellen, einschließlich:

  • Platziere alles in einem Modul.
  • Entferne die Num i Instanzbeschränkung und definiere einfach eine Costly Int Instanz.

Leider sind die Analoga dieser Lösungen in meinem Programm nicht durchführbar - ich habe mehrere Module, die den Klassenwert in seiner polymorphen Form verwenden, und nur in der Top-Level-Quelldatei werden schließlich konkrete Typen verwendet.

>

Es gibt auch Änderungen, die nicht die Anzahl der Auswertungen verringern, zB:

  • Verwenden von INLINE, INLINABLE oder NOINLINE für die Definition von costly in der Instanz. (Ich habe nicht erwartet, dass das funktioniert, aber hey, einen Versuch wert.)
  • Verwendung eines SPECIALIZE instance Costly Int Pragmas in der Instanzdefinition.

Letzteres ist überraschend für mich - ich hatte erwartet, dass es im Wesentlichen dem zweiten Punkt oben entspricht, der funktioniert . Das heißt, ich dachte, es würde ein spezielles Costly Int -Wörterbuch generieren, das alle von foo , bar und costlyInt teilen würden. Meine Frage 2: Was fehlt mir hier?

Meine letzte Frage: Gibt es einen relativ einfachen und narrensicheren Weg, um zu bekommen, was ich will, d. h. alle Verweise auf costly eines bestimmten konkreten Typs werden über Module verteilt? Von dem, was ich bisher gesehen habe, vermute ich, dass die Antwort nein ist, aber ich halte immer noch Hoffnung.

    
Chris Peikert 18.05.2015, 21:12
quelle

1 Antwort

6

Die Steuerung der Freigabe ist in GHC schwierig. Es gibt viele Optimierungen, die GHC macht, was sich auf die gemeinsame Nutzung auswirken kann (z. B. Inlining, Ausreißer usw.).

In diesem Fall, um die Frage zu beantworten, warum das SPECIALISE-Pragma den beabsichtigten Effekt nicht erreicht hat, schauen wir uns den Kern des B-Moduls an, insbesondere die Funktion bar :

%Vor%

Das hat nicht so funktioniert, wie wir es wollten. Anstatt costly wiederzuverwenden, entschied sich GHC dafür, die costly -Funktion einfach einzufügen.

Wir müssen also verhindern, dass GHC teuer wird, oder die Berechnung wird dupliziert. Wie machen wir das? Sie könnten denken, dass das Hinzufügen eines {-# NOINLINE costly #-} pragma ausreicht, aber leider scheint Spezialisierung ohne Inlining nicht gut zusammen zu funktionieren:

%Vor%

Aber es gibt einen Trick, GHC zu überzeugen, das zu tun, was wir wollen: wir können costly folgendermaßen schreiben:

%Vor%

Dies erlaubt uns, costly zu spezialisieren, und vermeidet gleichzeitig das Inlining unserer Berechnungen.

    
bennofs 19.05.2015, 18:25
quelle