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 宏:引用和语法引用的主要内容,如果未能解决你的问题,请参考以下文章

中文书籍中对《人月神话》的引用(十三):Clojure编程软件设计重构软件领导……

理解clojure语法

如何Clojure.Spec引用类型(如原子)?

Clojure 宏

在clojure中,如何将宏应用于列表?

Clojure宏:从地图创建局部变量[重复]