2012-04-06 4 views
7

Gibt es eine Möglichkeit, GHC zu zwingen, eine bestimmte Berechnung für die Lebensdauer eines bestimmten Werts zu treffen?Gibt es eine implizite ish-Memoisierung in Haskell?

Ich könnte offensichtlich den Wert in einen Datensatz legen, Lazy Record-Einträge für das Ergebnis der Berechnung erstellen, und erstellen Sie eine Maker-Funktion, die den Datensatz erstellt und den Wert in die Einträge thunks.

Ich würde es hassen, den ursprünglichen Wert aus der Aufzeichnung jedes Mal ziehen, wenn ich es wollte. Und Haskell hat keine ad hoc polymorphen Is-A-Beziehungen wie C++ oder Java.

Gibt es einen Trick für das Memo von Werten über mehrere unabhängige Aufrufe einer Funktion mit identischen Parametern?

Ich konnte mir vage verschiedene Tricks mit Formen von abhängigen Typen vorstellen, die dem Compiler mehrere Nutzungen mitteilen würden. Es gibt keine abhängigen Typen in Haskell, aber vielleicht etwas um implizite Parameter? Ich vermute nicht, aber ich dachte, ich würde fragen. Ein Pragma vielleicht?


Imagine ich einen Vektor von Necklace Datenstrukturen haben, für die ich einen resultierenden Vektor der rationalen Zahlen benötigen, als gemeinsamen Nenner und einen Vektor von Zählern gespeichert.

{-# LANGUAGE ImplicitParams #-} 
import qualified Data.Vector as V 

data Necklace = Necklace { ... } 
necklace_length n = ... 

denominator :: (necklaces :: V.Vector Necklace) => Int 
denominator = V.foldl' lcm 30 $ V.map necklace_length ?necklaces 

numerators :: (necklaces :: V.Vector Necklace) => V.Vector Int 
numerators = V.map f ?necklaces 
    where f x = ... denominator ... 

kittytoy :: (necklaces :: V.Vector Necklace) => Meow -> ... 
kittytoy = \meow -> ... numerators ... 

A priori, ich erwarten würde, wenn ich kittytoy mehrere Millionen Mal aufrufen, die jeweils mit einem anderen Parameter meow, dann erzeugt GHC-Code, der numerators eine Million Mal, jeweils mit einer identischen impliziten Parameter necklaces aufruft.

Es ist dennoch offensichtlich, dass numerators nur einmal aufgerufen werden muss, das erste mal ?necklaces zugewiesen wird, was bedeutet, dass GHC möglicherweise diese Optimierung bemerken könnte.

Es sollte sogar einen expliziten Code-Refactoring-Ansatz geben, der Vorlagenhaskell verwendet, um die Thunks explizit zu übergeben, indem Code wie ?numerators = numerators erzeugt und numerators :: V.Vector Int hinzugefügt wird, um Einschränkungen von Funktionen einzugeben, die ihn aufrufen.

+0

'kittytoy halsketten = lassen n = zähler halsketten in \ meow -> stuff 'sollte auf jeden fall memoize' n'. Nicht sicher, ob es auch so ist wie es ist. – yairchu

+0

Meinst du 'let k = kittytoy in ...' ersetzt jeden Anruf zu 'kittytoy' mit' k'? Ja, ich stimme zu, dass ich den impliziten Parameter "Halsketten" notieren sollte, aber sollte ich das wirklich tun müssen? –

+0

Ahh, nein, ich versuche, 'Zähler' * außerhalb * des Aufrufs von' kittytoy' zu merken, weil 'kittytoy' selbst mehrere Millionen Male aufgerufen wird. Ja, das ist ein gültiger Punkt, dass 'kittytoy' unabhängig davon ein Lambda-Ausdruck sein sollte, ich werde es aus Gründen der Klarheit bearbeiten. –

Antwort

7

Sie suchen wahrscheinlich nach einer reinen Memoisierung, wie von data-memocombinators implementiert. Im Grunde funktioniert es, indem eine (faule, möglicherweise unendliche) Baumstruktur mit allen möglichen Werten der Funktion an jedem Blatt erstellt wird und dann eine neue Funktion erstellt wird, die einfach auf den Wert an der relevanten Stelle zugreift. Zum Beispiel können Sie eine memoiser für Funktionen Bool -> a wie folgt schreiben können:

memoBool :: (Bool -> a) -> (Bool -> a) 
memoBool f = 
    let fTrue = f True 
     fFalse = f False 
    in \b -> if b then fTrue else fFalse 

In diesem Fall ist der „Baumstruktur“ eher Bonsai, mit nur zwei Blätter.

Daten memocombinators diese Pakete im Memo a Typ auf, wie forall r. (a -> r) -> (a -> r) definiert, mit nützlichen combinators wie pair :: Memo a -> Memo b -> Memo (a, b) (sprich: wenn Sie Funktionen Argument Typ a und memoise Funktionen Argument Typ b memoise können, können Sie memoise Funktionen Argumenttyp (a, b)).

Dies ist rein und ziemlich elegant und beruht auf dem Teilen, das von im Grunde genommen allen Haskell-Implementierungen implementiert wird (was dasselbe ist, das deine Plattenidee funktioniert).Leider ist es auch nicht sehr schnell, daher sollten Sie für den praktischen Gebrauch stattdessen uglymemo verwenden, das eine veränderbare Map hinter den Kulissen verwendet (aber eine extern-reine Schnittstelle verfügbar macht).

+0

Ich denke nicht, obwohl es schön ist zu wissen, dass es existiert. Ich möchte, dass das Memo gelöscht wird, wenn sich der implizite Parameter "Halsketten" ändert. Ich fand Yairchu Kommentar klärende, grundsätzlich implizite Parameter könnten zusätzliche Möglichkeiten, sondern nur durch explizite Lambda-Ausdrücke zu schaffen. Es fühlt sich an wie mehr möglich sein sollte, aber okay. –

+0

Nun, du würdest einfach 'Zähler schreiben :: V.Vector Necklace -> V.Vector Int'. Dann würden Aufrufe mit demselben Parameter das gleiche Ergebnis ohne Neuberechnung erzeugen. Zugegeben, abhängig davon, wie groß Ihr 'Vektor' ist, können die Kosten für das Nachschlagen in einer Baumstruktur die Kosten der Durchführung der' Zähler'-Berechnung von Grund auf übersteigen. GHC verfügt über die erforderlichen Funktionen zum Erstellen einer Memoisierungsbibliothek basierend auf der Zeigeridentität der Eingabewerte, aber ich weiß nicht, ob Hackage dafür vorhanden ist. – ehird

+0

Übrigens sind implizite Parameter eine sehr seltene Erweiterung, und die meisten Menschen vermeiden sie - ich habe nur zwei Codeabschnitte gesehen, die sie tatsächlich verwenden, und sie haben fiese Fallstricke, wie das Hinzufügen einer Typsignatur zu einer Definition, die ihren Wert ändert . – ehird

0

Es gibt eine andere plausible Ansatz, die dadurch entstehen, dass type jetzt definiert Synonyme für Typ Constraints, wie answer here in Philip JF ‚s zur Kenntnis genommen.

Sie wahrscheinlich ein Synonym für eine Art Zwang schaffen könnte, die für die verschiedenen abgeleiteten Werte impliziten Parameter erstellt:

type Necklaces = (necklaces :: V.Vector Necklace, 
        denominator :: Int, 
        numerators :: V.Vector Int) 

kittytoy :: (Necklaces) => Meow -> ... 

Sie würden zunächst alle Werte wie ?numerators zuweisen eine Vorlage Haskell Bau von irgendeiner Form mit . Ich werde damit spielen, um zu sehen, ob es funktioniert.