Gibt es eine einfachere Möglichkeit, ein rekursives Fn zu memozieren?

9

Nehmen wir an, Sie haben eine rekursive Funktion, die in einem let-Block definiert ist:

%Vor%

Dies kann mechanisch transformiert werden, um memoize zu verwenden:

  1. Wickeln Sie das Formular fn in einem Aufruf in memoize .
  2. Verschieben Sie den Funktionsnamen als erstes Argument.
  3. Übergeben Sie die Funktion in sich selbst, wo immer sie heißt.
  4. Erneutes Binden des Funktionssymbols mit partial .

Die Umwandlung des obigen Codes führt zu:

%Vor%

Das funktioniert, fühlt sich aber zu kompliziert an. Die Frage ist: Gibt es einen einfacheren Weg?

    
Mike Fikes 12.12.2014, 14:34
quelle

4 Antworten

3

Ich gehe Risiken ein, da ich kein Gelehrter bin, aber das glaube ich nicht. Sie haben so ziemlich das Standard-Ding gemacht, was im Endeffekt eine partielle Anwendung von Memoization durch einen Fixpunktkombinator ist.

Sie könnten jedoch versuchen, mit Makros herumzuhantieren (in einfachen Fällen könnte es einfach sein, Syntax-Quote würde Namensauflösung für Sie tun und Sie könnten damit arbeiten). Ich werde es versuchen, sobald ich nach Hause komme.

edit: ging nach Hause und probierte Sachen aus, das scheint für einfache Fälle ok zu sein

%Vor%

einfacher als das, was ich dachte!

    
freakhill 12.12.2014, 15:11
quelle
1

Ich bin mir nicht sicher, ob das per se "einfacher" ist, aber ich dachte, ich würde einen Ansatz teilen, den ich für die Implementierung von letfn für einen CPS-Transformator, den ich geschrieben habe, verwendete.

Der Schlüssel besteht darin, die Variablen einzuführen, aber die Zuweisung der Werte zu verzögern, bis sie alle im Gültigkeitsbereich sind. Was du eigentlich schreiben möchtest ist:

(let [f nil] (set! f (memoize (fn [] <body-of-f>))) (f))

Natürlich funktioniert das nicht wie es ist, weil let bindings in Clojure unveränderbar sind. Wir können das aber umgehen, indem wir einen Referenztyp verwenden - zum Beispiel ein promise :

(let [f (promise)] (deliver! f (memoize (fn [] <body-of-f>))) (@f))

Aber das ist noch zu kurz, denn wir müssen jede Instanz von f in <body-of-f> durch (deref f) ersetzen. Aber wir können dies lösen, indem wir eine andere Funktion einführen, die die im Versprechen gespeicherte Funktion aufruft. Die gesamte Lösung könnte also so aussehen:

(let [f* (promise)] (letfn [(f [] (@f*))] (deliver f* (memoize (fn [] <body-of-f>))) (f)))

Wenn Sie eine Reihe von gegenseitig rekursiven Funktionen haben:

(let [f* (promise) g* (promise)] (letfn [(f [] (@f*)) (g [] (@g*))] (deliver f* (memoize (fn [] (g)))) (deliver g* (memoize (fn [] (f)))) (f)))

Offensichtlich ist das eine Menge Kesselplatte. Aber es ist ziemlich trivial, ein Makro zu konstruieren, das Ihnen letfn -Stil -Syntax gibt.

    
Nathan Davis 13.12.2014 00:05
quelle
1

Ja, es gibt einen einfacheren Weg. Es ist keine funktionale Transformation, sondern baut auf der in clojure erlaubten Verunreinigung auf.

%Vor%

Der erste Schritt definiert fib fast normal, aber rekursive Aufrufe werden mit durchgeführt, unabhängig davon, welche Variable fib enthält . Dann wird fib neu definiert und wird zur Memo-Version seines alten Selbst.

    
clojureman 29.03.2018 10:20
quelle
0

Ich würde annehmen, dass clojure idiomatische Weise recur

verwendet %Vor%     
fixxer 29.03.2018 15:47
quelle

Tags und Links