Clojure 宏:引用和语法引用
Posted
技术标签:
【中文标题】Clojure 宏:引用和语法引用【英文标题】:Clojure macros: quoting and syntax quoting 【发布时间】:2015-09-06 20:30:25 【问题描述】:假设我有以下代码:
(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】:当您使用test2
时,它将取消引用整个表单(pos? x)
,如果它是一个常量或可能是一个已经定义的全局变量,它将在编译时工作,但如果您传递一个词法范围的变量名,则不会还不存在。
因此,你真的想要这个:
(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
是一个我们可以在编译时使用的数字。我不确定你是否会注意到速度的提高,因为这些算法算不上繁重的算法。
【讨论】:
感谢您的快速回答!关于引用 x 符号,是的,这是我忽略的错误。但是,我的目标是完全摆脱扩展表达式中的条件,就像在 test1 宏中一样。出于性能原因,我希望返回的函数完全独立于 x 或 x 的值(同样,这可能不是问题)。 @ViktorM。我添加了一个删除if
运行时间的版本,但正如您所见,您不能再将它与变量一起使用。
不能使用变量,这很糟糕。我在返回函数中使用的算法会更复杂一些,每秒可能会被调用数百或数千次,这就是我想尽可能多地挤出的原因。看来我必须坚持简单的引用!
@ViktorM。希望您从 profiling 开始搜索。以上是关于Clojure 宏:引用和语法引用的主要内容,如果未能解决你的问题,请参考以下文章