Clojure宏:引用和语法引用
假设我有以下代码:
(defmacro test1 [x]
(list 'fn '[y]
(if (pos? x)
'(println y)
'(println (- y)))))
它做我需要的,根据x编写一个函数,并且不会引用x。 例如, (test1 1)
宏展开为(fn* ([y] (println y)))
。
现在,我想用语法引用来重写它。 这是我到目前为止:
(defmacro test2 [x]
`(fn [y#]
(if ~(pos? x)
(println y#)
(println (- y#)))))
这完全一样,只有一个例外:它在展开的表达式中留下了一个(if true ..)
表达式:
(fn* ([y__12353__auto__] (if true (clojure.core/println y__12353__auto__) (clojure.core/println (clojure.core/- y__12353__auto__)))))
如果编译器可以优化它,这可能不是问题。 不过,有没有办法可以省略它?
当你使用test2
,它将取消引用整个表单(pos? x)
,它将在编译时工作,如果它是一个常数或可能已经定义的gloabl,但是如果你传递一个不存在的词汇范围变量名然而。
因此,你真的想要这个:
(defmacro test2 [x]
`(fn [y#]
(if (pos? ~x) ; just unquote x, not the whole predicate expression
(println y#)
(println (- y#)))))
(macroexpand '(test2 y))
; ==>
; (fn* ([y__1__auto__]
; (if (clojure.core/pos? y)
; (clojure.core/println y__1__auto__)
; (clojure.core/println (clojure.core/- y__1__auto__)))))
(defn test-it []
(let [y -9]
(test2 y)))
((test-it) 5) ; prints "-5"
随意尝试与您的版本。 (提示:你会得到一个异常,因为clojure.lang.Symbol不能转换为java.lang.Number)
UPDATE
既然你想基于一个常量来创建函数,你需要写一点不同的东西:
(defmacro test3 [x]
(assert (number? x) "needs to be a compile time number")
(if (pos? x)
`(fn [y#] (println y#))
`(fn [y#] (println (- y#)))))
现在如果你使用(test3 x)
你会得到一个错误,因为x
不是一个数字,而是在评估时得到你想要的(test3 -10)
因为-10
是一个我们可以使用编译时间的数字。 我不确定你会注意到速度的提高,因为这些算法不算繁重。