7

J'essaie d'utiliser Clojure pour générer dynamiquement des fonctions qui peuvent être appliquées à d'importants volumes de données - à savoir une exigence est que les fonctions soient compilées en bytecode afin d'exécuter rapidement, mais leur La spécification n'est pas connue avant l'exécution.générer dynamiquement des fonctions de haute performance dans clojure

par exemple. suppose que je précise les fonctions d'un simple DSL comme:

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

Je voudrais créer une fonction de compilation spécifications telles que:

(compile-spec my-spec) 

renverrait une fonction compilée d'un paramètre x qui retourne 2x + 3.

Quelle est la meilleure façon de le faire en Clojure?

Répondre

11

Hamza Yerlikaya a déjà fait le point le plus important, à savoir que le code Clojure est toujours compilé. Je suis juste ajouter une illustration et quelques informations sur certains fruits mûrs pour vos efforts d'optimisation.

Tout d'abord, le point ci-dessus à propos du code de Clojure toujours en cours de compilation comprend des fermetures retournées par des fonctions d'ordre supérieur et les fonctions créées en appelant eval sur fn/fn* formes et fait tout ce qui peut agir en fonction Clojure. Ainsi, vous n'avez pas besoin d'un DSL séparé pour décrire les fonctions, il suffit d'utiliser les fonctions d'ordre supérieur (et peut-être des macros):

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

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

choses serait plus intéressant si vos spécifications devaient inclure des informations sur les types de paramètres, comme alors vous pourriez être intéressé par l'écriture d'une macro pour générer du code en utilisant ces conseils de type. L'exemple le plus simple que je peux penser serait une variante de ce qui précède:

(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 

Utilisez :int, :long, :float ou :double (ou les symboles non-espace de nom qualifié de noms correspondants) comme premier argument pour tirer profit d'arithmétique primitive non mise en boîte appropriée pour vos types d'argument. En fonction de ce que fait votre fonction, cela peut vous donner un coup de pouce très significatif.

D'autres types de conseils sont normalement fournis avec la syntaxe #^Foo bar (^Foo bar fait la même chose en 1.2); si vous voulez les ajouter au code de macro généré, enquêter sur la fonction with-meta (vous aurez besoin de fusionner '{:tag Foo} dans les métadonnées des symboles représentant les arguments formels à vos fonctions ou let habitants -introduced que vous souhaitez mettre des notes de type sur).


Oh, et si vous souhaitez toujours savoir comment mettre en œuvre votre idée originale ...

Vous pouvez toujours construire l'expression Clojure pour définir votre fonction - (list 'fn ['x] (a-magic-function-to-generate-some-code some-args ...)) - et appel eval sur le résultat.Cela vous permettrait de faire quelque chose comme le suivant (il serait plus simple d'exiger que la spécification inclue la liste des paramètres, mais voici une version supposant que les arguments doivent être extraits de la spécification, sont tous appelés paramFOO et doivent être triés lexicographiquement):

(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 

la grande majorité du temps, il n'y a aucune raison de faire les choses de cette façon et il devrait être évité; utiliser des fonctions et des macros d'ordre supérieur à la place. Cependant, si vous faites quelque chose comme, par exemple, la programmation évolutive, alors elle est là, offrant la flexibilité ultime - et le résultat est toujours une fonction compilée.

+1

Ceci est une excellente réponse: m'a aidé à comprendre ce qui se passe sous le capot et résout parfaitement le problème. Merci beaucoup Michal! – mikera

+0

Heureux de vous aider. :-) –

6

Même si vous ne compilez pas AOT votre code, dès que vous définissez une fonction qu'il se compile en bytecode à la volée.