7

Я пытаюсь использовать Clojure для динамического создания функций, которые могут применяться к большим объемам данных, т. Е. Требование состоит в том, чтобы функции были скомпилированы для байт-кода для быстрого выполнения, но их спецификация неизвестна до времени выполнения.Динамическое создание высокопроизводительных функций в clojure

например. Предположим, я указываю функции с простой DSL, как:

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

Я хотел бы создать функцию компиляции спецификации, такие, что:

(compile-spec my-spec) 

вернуть бы скомпилированный функцию одного параметра х, который возвращает 2x + 3.

Каков наилучший способ сделать это в Clojure?

ответ

11

Hamza Yerlikaya уже сделал самый важный момент, который является кодом Clojure всегда составлен. Я просто добавляю иллюстрации и некоторую информацию о некоторых низко висящих фруктах для ваших усилий по оптимизации.

Во-первых, выше пункт о коде Clojure всегда компилируется включает в себя закрытие возвращаемые функций и функций, созданных более высокого порядка по телефону eval на fn/fn* формах и в самом деле что-либо другое, что может выступать в качестве функции Clojure. Таким образом, вам не нужен отдельный DSL для описания функций, просто использовать функции высшего порядка (и, возможно, макросы):

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

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

вещей будет более интересно, если ваши данные были включать информацию о типах параметров, а то вам может быть интересно написать макрос для генерации кода с помощью этих типов подсказок. Простейший пример, который я могу думать будет вариант выше:

(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 

Использование :int, :long, :float или :double (или не-пространства имен, символы соответствующих имен) в качестве первого аргумента, чтобы воспользоваться несвязанной примитивной арифметики, подходящей для ваших типов аргументов. В зависимости от того, что делает ваша функция, это может дать вам очень значительное повышение производительности.

Другие типы подсказок обычно снабжены синтаксисом #^Foo bar (^Foo bar делает то же самое в 1.2); если вы хотите добавить их к макросгенерированному коду, исследуйте функцию with-meta (вам нужно объединить '{:tag Foo} в метаданные символов, представляющих формальные аргументы, вашим функциям или let - введенные местные жители, на которые вы хотите поместить подсказки типа).


О, и в случае, если вы все еще хотите знать, как реализовать свою оригинальную идею ...

Вы всегда можете построить выражение Clojure, чтобы определить свою функцию - (list 'fn ['x] (a-magic-function-to-generate-some-code some-args ...)) - и вызов eval на результат.Это позволит вам сделать что-то вроде следующего (было бы проще потребовать, чтобы спецификация включала список параметров, но вот версия, предполагающая, что аргументы должны быть выведены из спецификации, все называются paramFOO и должны быть лексикографически отсортированы):

(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 

Подавляющее большинство времени, нет веских оснований для этого, и его следует избегать; вместо этого используйте функции более высокого порядка и макросы. Однако, если вы делаете что-то вроде, скажем, эволюционного программирования, то оно есть, обеспечивая максимальную гибкость - и результат все еще является скомпилированной функцией.

+1

Это отличный ответ: помог мне понять, что происходит под капотом и прекрасно решает проблему. Большое спасибо Михалу! – mikera

+0

Счастливые помочь. :-) –

6

Даже если вы не компилируете свой код AOT, как только вы определите функцию, он скомпилируется в байт-код на лету.

Смежные вопросы