Macros 如何避免Clojure中的回指宏?

Macros 如何避免Clojure中的回指宏?,macros,clojure,Macros,Clojure,以这个(简化的)例子: 现在符号nv是硬编码的。有没有办法让gensymnv在地图功能中仍然可以使用它 顺便问一下,这实际上是一个回指宏吗?在您的示例中,一些calc和map都发生在宏扩展期间,而不是在运行时,因此nv无论如何都不需要是let。宏本身编写不正确,与符号捕获无关。据我理解,它实际上不是一个回指宏 对应的回指会给你一个语法,如: (make foo 1 2 3 4 it 6 7 it 8 9) i、 e.已定义符号it,以便在make宏的主体内使用 我不确定这是否是您想要的,因为我

以这个(简化的)例子:

现在符号
nv
是硬编码的。有没有办法让gensym
nv
在地图功能中仍然可以使用它


顺便问一下,这实际上是一个回指宏吗?

在您的示例中,
一些calc
map
都发生在宏扩展期间,而不是在运行时,因此
nv
无论如何都不需要是
let
。宏本身编写不正确,与符号捕获无关。

据我理解,它实际上不是一个回指宏

对应的回指会给你一个语法,如:

(make foo 1 2 3 4 it 6 7 it 8 9)
i、 e.已定义符号
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
,那么您并不真的需要宏:最好只使用:


答案包含在问题中:只要像Clojure没有自动gensym一样使用
gensym

(defmacro make [v & body]
  (let [value-sym (gensym)]
    `(let [~value-sym ~(some-calc v)]
       ~@(replace {:value value-sym} body))))
请注意,我不确定您是否真的想要
~
~@
在这里-这取决于
body
应该是在
let
中执行的表达式序列,还是单个函数调用的参数序列。但是,
~@
会更加直观/正常,所以这就是我要猜测的

这个宏是否是回指有点可疑:肯定是将
nv
引入调用范围was,但这基本上是无意的,所以我会说不。在我的修订版中,我们不再引入
nv
或类似的内容,而是“神奇地”将
:value
替换为
v
。不过,我们只在主体的最顶层这样做,所以这不像引入真正的作用域——我想说,这更像是让客户机的代码在极端情况下意外中断

关于这种欺骗行为如何突然出现的例子,想象一下
body
的一个元素是
(inc:value)
。它不会被宏替换,将扩展到
(inc:value)
,这永远不会成功。因此,我建议使用真正的回指宏,它为符号引入了真正的范围。差不多

(defmacro make [v & body]
  `(let [~'the-value ~(some-calc v)]
     ~@body))

然后调用者可以在他们的代码中使用
,它的行为就像一个真实的、常规的本地绑定:您的宏通过魔术引入它,但它没有任何其他特殊技巧。

一种方法是使用动态绑定

(declare ^:dynamic *nv*)

(defmacro make [v & body]
  `(binding [*nv* ~(some-calc v)]
     ~(map #(if (= % :value) *nv* %) body)))
在实践中,当动态变量的作用域太宽时(测试和调试程序等比较困难),动态变量会变得很糟糕,但在这种情况下,如果作用域仅限于需要回指的本地调用上下文,则动态变量会非常方便


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

正如我所说,这是一个简化的示例;宏实际上返回一个将被相对频繁地调用的
fn
,因此在let中使用“缓存”。我真的不相信这一点,Alex-很明显,他的
map
计划在宏扩展期间发生,而且
某些计算也可能发生。例如,可能
(some calc'x)
返回
”(inc(doto x prn))
-非常适合在该上下文中使用,并且需要
let
以避免重复打印的副作用。当然,在扩展期间调用
map
some calc
可能是正确的。我的观点是
nv
let
实际上没有任何作用,因此宏本身对于
nv
的问题没有任何意义。尽管我接受了amalloy的回答,但这非常有帮助,谢谢!不确定是否可以将其称为回指:因此,
nv
应该引用宏调用表单的某些部分。如果从宏体中分解出
some calc
,则会有一个更合适的回指调用:
(make(some calc x)(dose to nv))
其中
nv
指的是
(some calc x)
。在你的例子中,你会有
(make x(doe thing to nv))
,但是
nv
不会直接引用宏调用表单中的任何内容。@skuro:我明白了,所以这基本上是两个世界中最糟糕的一个-引入一个新符号,但它将一直隐藏,直到有人意外地重新定义它。Hf跟踪缺陷:)幸运的是,根据amalloy的建议,显式的
gensym
解决了这个问题。是的,我最近开始(ab)使用动态绑定,并且仍在探索它们可能有用的案例。谢谢你的主意!
(defmacro make [v & body]
  `(let [~'the-value ~(some-calc v)]
     ~@body))
(declare ^:dynamic *nv*)

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