Hamza Yerlikaya 已经提出了最重要的一点,那就是 Clojure 代码是总是编译的。我只是为您的优化工作添加一个插图和一些关于一些容易实现的目标的信息。
首先,关于 Clojure 代码始终被编译的上述观点包括由高阶函数返回的闭包和通过在 fn / fn* 表单上调用 eval 创建的函数以及实际上任何其他可以充当 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-introduced locals 您希望添加类型提示)。
哦,如果你还想知道如何实现你最初的想法......
您始终可以构造 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 [~@(sort @params)] ~@spec))))
(def my-spec '[(+ (* 31 param0) 47)])
((compile-spec my-spec) 5)
; => 202
绝大多数时候,没有充分的理由这样做,应该避免;改用高阶函数和宏。但是,如果您正在做类似进化编程之类的事情,那么它就在那里,提供了最大的灵活性——结果仍然是一个编译函数。