7

Sto provando ad usare Clojure per generare dinamicamente funzioni che possono essere applicate a grandi volumi di dati - cioè un requisito è che le funzioni siano compilate in bytecode per essere eseguite velocemente, ma il loro la specifica non è nota fino al tempo di esecuzione.Generazione dinamica di funzioni ad alte prestazioni in clojure

ad es. Suppongo che ho specificare le funzioni con un semplice DSL come:

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

vorrei creare una funzione di compilazione-spec tale che:

(compile-spec my-spec) 

restituirebbe una funzione compilata di un parametro x che restituisce 2x + 3.

Qual è il modo migliore per farlo in Clojure?

risposta

11

Hamza Yerlikaya ha già fatto il punto più importante, ovvero che il codice Clojure è sempre compilato. Sto solo aggiungendo un'illustrazione e alcune informazioni su alcuni frutti a basso impatto per i tuoi sforzi di ottimizzazione.

In primo luogo, il punto di cui sopra sul codice di Clojure sempre in fase di compilazione comprende chiusure restituiti dalle funzioni e funzioni create di ordine superiore chiamando eval su fn/fn* forme e in effetti qualsiasi altra cosa che può agire come una funzione Clojure. Quindi non hai bisogno di un modem DSL separato per descrivere le funzioni, basta usare le funzioni di ordine superiore (e possibilmente macro):

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

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

cose sarebbe più interessante se le caratteristiche del tuo dovessero includere le informazioni sui tipi di parametri, come quindi potresti essere interessato a scrivere una macro per generare codice usando quei suggerimenti tipo. L'esempio più semplice che posso pensare sarebbe una variante di quanto sopra:

(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 

Uso :int, :long, :float o :double (oi simboli non namespace qualificato di nomi corrispondenti) come primo parametro di sfruttare di aritmetica primitiva unboxed appropriata per i tuoi tipi di argomento. A seconda di cosa sta facendo la tua funzione, questo potrebbe darti un notevole incremento delle prestazioni.

Altri tipi di suggerimenti sono normalmente forniti con la sintassi #^Foo bar (^Foo bar fa la stessa cosa in 1.2); se vuoi aggiungerli al codice generato da una macro, investiga la funzione with-meta (dovrai unire '{:tag Foo} nei metadati dei simboli che rappresentano gli argomenti formali alle tue funzioni o let -introdotti i locals a cui vuoi mettere suggerimenti di tipo).


Oh, e nel caso in cui si desidera ancora di sapere come implementare la vostra idea originale ...

È sempre possibile costruire l'espressione Clojure per definire la vostra funzione - (list 'fn ['x] (a-magic-function-to-generate-some-code some-args ...)) - e la chiamata eval sul risultato.Ciò ti consentirebbe di fare qualcosa di simile al seguente (sarebbe più semplice richiedere che la specifica includesse l'elenco dei parametri, ma qui è una versione che presuppone che gli argomenti debbano essere pescati dalle specifiche, tutti sono chiamati paramFOO e devono essere ordinati lessicograficamente):

(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 

Nella stragrande maggioranza del tempo, non vi è alcuna buona ragione per fare le cose in questo modo e dovrebbe essere evitato; utilizzare invece funzioni e macro di ordine superiore. Tuttavia, se stai facendo qualcosa come, ad esempio, la programmazione evolutiva, allora è lì, fornendo la massima flessibilità - e il risultato è ancora una funzione compilata.

+1

Questa è una grande risposta: mi ha aiutato a capire cosa sta succedendo sotto il cofano e risolve il problema perfettamente. Mille grazie Michal! – mikera

+0

Felice di aiutare. :-) –

6

Anche se AOT non compila il codice, non appena si definisce una funzione, esso viene compilato in bytecode al volo.