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:
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:
costly
in der Instanz. (Ich habe nicht erwartet, dass das funktioniert, aber hey, einen Versuch wert.) 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.
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
:
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:
Aber es gibt einen Trick, GHC zu überzeugen, das zu tun, was wir wollen: wir können costly
folgendermaßen schreiben:
Dies erlaubt uns, costly
zu spezialisieren, und vermeidet gleichzeitig das Inlining unserer Berechnungen.
Tags und Links haskell polymorphism memoization