Clojure宏:引用和语法引用

时间:2015-09-06 20:30:25

标签: clojure macros quoting

说我有以下代码:

(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__)))))

如果编译器可以优化它,这可能不是问题。不过,有没有办法可以省略它?

1 个答案:

答案 0 :(得分:2)

当你使用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 ),您将收到异常

<强>更新

由于您希望基于常量来创建函数,因此需要稍微改写它:

(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是一个数字我们可以使用编译时间。我不确定你会注意到速度的提高,因为这些算法并不重要。