Clojure 宏:引用和语法引用

Clojure macros: quoting and syntax 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__)))))

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

当你使用 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 是我们可以在编译时使用的数字。我不确定您是否会注意到速度的提高,因为这些算法并不复杂。