Clojure宏:解析变量
在Clojure中,我似乎对宏了如指掌。我可能缺少一些基本的东西。首先,请允许我描述一个我想要的例子Clojure宏:解析变量,clojure,macros,Clojure,Macros,在Clojure中,我似乎对宏了如指掌。我可能缺少一些基本的东西。首先,请允许我描述一个我想要的例子 (defmacro macrotest [v] `(d ...)) (def a '(b c)) (macroexpand-1 '(macrotest a)) ; => (d (b c)) 换句话说,传递给macrotest的var被解析,但没有进一步评估 为宏提供var的值: (defmacro macrotest [v] `(d ~v)) (macroexpand-1 '(macro
(defmacro macrotest [v] `(d ...))
(def a '(b c))
(macroexpand-1 '(macrotest a))
; => (d (b c))
换句话说,传递给macrotest
的var
被解析,但没有进一步评估
为宏提供var的值:
(defmacro macrotest [v] `(d ~v))
(macroexpand-1 '(macrotest (b c)))
; => (d (b c))
但提供var不会:
(def a '(b c))
(macroexpand-1 '(macrotest a))
; => (d a)
是否可以在Clojure宏中解析var
,但不计算其值
编辑:我想要的似乎可以通过eval
:
(defmacro macrotest [v] `(d ~(eval v)))
(def a '(b c))
(macroexpand-1 '(macrotest a))
; => (user/d (b c))
重要的是要理解宏是在编译时计算的,而不是在运行时 在编译时,var a还没有值,因此它的值不会传递给宏,只传递给它的名称。然而,当您显式地传递“值”时,那么它在编译时就存在了,并且您可以看到它“工作” 词汇环境 在宏中使用
eval
是不好的做法。当然,以下方法很有效:
(macroexpand-1 '(macrotest a))
; => (user/d (b c))
。。。但是,这并不像预期的那样有效:
(macroexpand-1 '(let [a "oh no"]
(macrotest a)))
; => (user/d (b c))
当然,您希望宏按照本地定义的方式计算a
,而不是使用绑定到它的全局变量,但是您不能,因为宏在正确的词汇上下文中不计算v
。这是理解宏的关键:宏接受代码并生成代码;由该代码操纵的任何数据尚未提供给您。换句话说,宏展开的时刻可能与其结果代码的求值时间完全无关:在宏执行时求值的任何内容都可能过早求值,即数据的相关性
你想做什么?
有一个名为macrotest
的表单,它应该接受一个参数v
,然后像应用了表单d
一样执行。但是:
不应评估(宏测试(bc))
,只需将其原封不动地复制以生成(bc)
(d(bc))
应该评估(宏测试a)
,它将生成一个引用的表单,并将该表单放入生成的代码中,同时返回a
(d(b c))
(b c)
,然后编写:
(defmacro macrotest [v] `(d ~v))
。。。这要求d
准备好处理引用的参数;或者你同意不评估这个论点。
使用与上述相同的d
,可以显式引用参数:
(defmacro macrotest [v] `(d '~v))
但是,如果
d
本身是一个不计算其参数的宏,则必须避免使用~v
前面的引号 谢谢。我知道宏是在编译时计算的,但我可能不理解这一点的所有含义。所以我最后一个问题的答案是“否”?没有办法将运行时值传递到宏中?我认为没有办法,除了eval
之类的方法。在lisps中,宏通常用于构建新的“语法”,因此,如果您发现在编译时确实必须具有运行时值,也许您应该重新考虑您试图实现的目标;)如果宏扩展为函数,是否可能?使用eval
似乎适用于我的用例(参见编辑的文章)。我不确定这是否是个好主意。回答得很好!我确实忘记了宏中使用的变量的范围。不过,我对你在答覆的第二部分所引述的话有所怀疑。如果我使用(defmacro macrotest[v]
(d'~v)`和(defa'(bc))
,那么(macrotest a)
扩展到(d(quote a))
,而不是(d(bc))
@jindrichm谢谢,关于最后一部分你是对的,我可能不是很清楚。这里的意图是,在第二种情况下,macrotest
根本不评估其参数,只对不带引号的文本数据起作用;然后,引用该数据并将其传递给d
。因此,将a
赋予它将不会对表达式求值(与大多数宏一样)。