7

Ich versuche Clojure zu verwenden, um dynamisch Funktionen zu generieren, die auf große Datenmengen angewendet werden können - dh eine Anforderung ist, dass die Funktionen in Bytecode kompiliert werden müssen, um schnell ausgeführt zu werden Spezifikation ist bis zur Laufzeit nicht bekannt.Dynamische Generierung von Hochleistungsfunktionen in clojure

z.B. Ich nehme an Funktionen mit einer einfachen DSL angeben:

(def my-spec [:add [:multiply 2 :param0] 3]) 

Ich möchte eine Funktion der Kompilierung spec so erstellen, dass:

(compile-spec my-spec) 

eine kompilierte Funktion eines Parameters x Würde zurück, die 2x kehrt + 3.

Was ist der beste Weg, dies in Clojure zu tun?

Antwort

11

Hamza Yerlikaya hat bereits den wichtigsten Punkt gemacht, der Clojure Code ist immer kompiliert. Ich füge nur eine Illustration und einige Informationen über einige niedrig hängenden Früchte für Ihre Optimierungsbemühungen hinzu.

Zum einen der oben genannten Punkt über Clojure Code enthält immer Verschlüsse kompiliert wird durch Funktionen höherer Ordnung und Funktionen von eval Aufruf auf fn/fn* Formen zurück erstellt und in der Tat alles andere, als Clojure Funktion wirken kann. Somit benötigen Sie keine separate DSL-Funktionen zu beschreiben, nur Funktionen höherer Ordnung verwenden (und möglicherweise Makros):

(defn make-affine-function [a b] 
    (fn [x] (+ (* a x) b))) 

((make-affine-function 31 47) 5) 
; => 202 

Dinge wäre interessanter, wenn Ihre Angaben Informationen über die Arten von Parametern enthalten waren, wie Dann könnten Sie daran interessiert sein, ein Makro zu schreiben, um mit diesen Typhinweisen Code zu generieren. Das einfachste Beispiel ich denken kann, wäre eine Variante der oben sein:

(defmacro make-primitive-affine-function [t a b] 
    (let [cast #(list (symbol (name t)) %) 
     x (gensym "x")] 
    `(fn [~x] (+ (* ~(cast a) ~(cast x)) ~(cast b))))) 

((make-primitive-affine-function :int 31 47) 5) 
; => 202 

Verwenden :int, :long, :float oder :double (oder die Nicht-Namespace-qualifizierten Symbole des entsprechenden Namen) als erstes Argument zu nutzen der ungepackten primitiven Arithmetik, die für Ihre Argumenttypen geeignet ist. Je nachdem, was Ihre Funktion macht, kann dies zu einer erheblichen Leistungssteigerung führen.

Andere Arten von Hinweisen sind normalerweise mit der #^Foo bar-Syntax zur Verfügung gestellt (^Foo bar macht das gleiche in 1.2); Wenn Sie sie zu Makro-generiertem Code hinzufügen möchten, untersuchen Sie die with-meta Funktion (Sie müssen '{:tag Foo} in die Metadaten der Symbole, die die formalen Argumente für Ihre Funktionen oder let -introduced Einheimische, die Sie Typhinweise setzen möchten, zusammenführen)).


Oh, und für den Fall, würden Sie noch gerne wissen, wie Ihre ursprüngliche Idee zu implementieren ...

Sie können immer den Clojure Ausdruck erstellen, um Ihre Funktion zu definieren - (list 'fn ['x] (a-magic-function-to-generate-some-code some-args ...)) - und Call eval über das Ergebnis.Das würde Ihnen erlauben, etwas wie das Folgende zu tun (es wäre einfacher zu verlangen, dass die Spezifikation die Parameterliste enthält, aber hier ist eine Version, die davon ausgeht, dass Argumente aus der Spezifikation herausgefischt werden sollen, werden paramFOO genannt und sollen lexikografisch sortiert werden):

(require '[clojure.walk :as walk]) 

(defn compile-spec [spec] 
    (let [params (atom #{})] 
    (walk/prewalk 
    (fn [item] 
     (if (and (symbol? item) (.startsWith (name item) "param")) 
     (do (swap! params conj item) 
      item) 
     item)) 
    spec) 
    (eval `(fn [[email protected](sort @params)] [email protected])))) 

(def my-spec '[(+ (* 31 param0) 47)]) 

((compile-spec my-spec) 5) 
; => 202 

Die überwiegende Mehrheit der Zeit, gibt es keinen guten Grund, Sachen auf diese Weise zu tun, und es sollte vermieden werden; Verwenden Sie stattdessen Funktionen und Makros höherer Ordnung. Wenn Sie jedoch etwas, sagen wir, evolutionäre Programmierung tun, dann ist es da und bietet die ultimative Flexibilität - und das Ergebnis ist immer noch eine kompilierte Funktion.

+1

Das ist eine großartige Antwort: hat mir geholfen zu verstehen, was unter der Haube vor sich geht und löst das Problem perfekt. Vielen Dank Michal! – mikera

+0

Gerne helfen. :-) –

6

Auch wenn Sie Ihren Code nicht AOT kompilieren, sobald Sie eine Funktion definieren, wird es kompiliert zu Bytecode im laufenden Betrieb kompiliert.

Verwandte Themen