采用这个(简化)示例:
(defmacro make [v & body]
`(let [~'nv ~(some-calc v)]
~(map #(if (= % :value) 'nv %) body)))
现在符号nv
是硬编码的。有没有办法以某种方式gensym nv
并仍然能够在地图功能中使用它?
顺便说一句,这实际上是一个照应的宏吗?
答案 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- *样式中很多)的常见习语的反转。在这个习语中(据我所知并不常见),绑定用于暴露宏隐藏的内容。