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