如何避免Clojure中的照应宏?

时间:2012-03-19 02:46:22

标签: macros clojure

采用这个(简化)示例:

(defmacro make [v & body]
  `(let [~'nv ~(some-calc v)]
     ~(map #(if (= % :value) 'nv %) body)))

现在符号nv是硬编码的。有没有办法以某种方式gensym nv并仍然能够在地图功能中使用它?

顺便说一句,这实际上是一个照应的宏吗?

3 个答案:

答案 0 :(得分:4)

答案包含在问题中:如果Clojure没有自动gensyms,就像使用gensym一样。

(defmacro make [v & body]
  (let [value-sym (gensym)]
    `(let [~value-sym ~(some-calc v)]
       ~@(replace {:value value-sym} body))))

请注意,我不确定您是否真的想要~~@ - 这取决于body是否应该是{{1}中要执行的表达式序列单个函数调用的参数序列。但是let会更加直观/正常,所以这就是我猜的。

这个宏是否是一个回指是有点可疑的:肯定会将~@引入调用范围,但它基本上是无意的,所以我会说不。在我的修订版中,我们不再介绍nv或类似内容,但我们“神奇地”将nv替换为:value。我们只在身体的最顶层做到这一点,所以它不像引入一个真正的范围 - 我会说它更像是让客户的代码在极端情况下突然中断。

有关如何突然出现这种欺骗行为的示例,请设想v的其中一个元素是body。它不会被宏取代,而是会扩展到(inc :value),这永远不会成功。所以我推荐一个真正的回指宏,它引入了一个符号的真实范围。像

这样的东西
(inc :value)

然后调用者可以在他们的代码中使用(defmacro make [v & body] `(let [~'the-value ~(some-calc v)] ~@body)) ,它的行为就像一个真实的,常规的本地绑定:你的宏通过魔法引入它,但它没有任何其他特殊技巧。 / p>

答案 1 :(得分:3)

根据我的理解,它实际上并不是一个照应的宏。

一个照应等同物会给你一个语法:

(make foo 1 2 3 4 it 6 7 it 8 9)

即。已定义符号it,以便可以在make宏的主体内使用它。

我不确定这是否是你想要的,因为我没有足够的关于如何使用这个宏的上下文,但是你可以实现上面的那样:

(defmacro make [v & body]
   `(let [~'it ~v]
      (list ~@body)))

(make (* 10 10) 1 2 3 4 it 6 7 it 8 9)
=> (1 2 3 4 100 6 7 100 8 9)

或者,如果您并不是真的想要创建新语法并且只想在某些集合中替换:value,那么您实际上并不需要宏:最好只使用{{3 }}:

(replace {:value (* 10 10)} [1 2 :value 3 4])
=> [1 2 100 3 4]

答案 2 :(得分:2)

一种方法是使用动态绑定。

(declare ^:dynamic *nv*)

(defmacro make [v & body]
  `(binding [*nv* ~(some-calc v)]
     ~(map #(if (= % :value) *nv* %) body)))

实际上,动态变量在它们的范围太宽时会很糟糕(测试和调试程序等更难),但在这种情况下,范围仅限于需要回指的本地调用环境,它们可以非常方便。

这种用法的一个有趣的方面是,它是使用宏来隐藏动态绑定(在with- *样式中很多)的常见习语的反转。在这个习语中(据我所知并不常见),绑定用于暴露宏隐藏的内容。