使用宏生成方法时出现奇怪的错误?

使用宏生成方法时出现奇怪的错误?

问题描述:

这里是一个简短的片段演示了这个问题:使用宏生成方法时出现奇怪的错误?

(defmulti test-dummy type) 

(defmacro silly [t] 
    `(defmethod test-dummy ~(resolve t) [some-arg] "FOO!")) 

(silly String) 

评估结果为“不能使用合格的名称作为参数:用户/一些-ARG”,但运行macroexpand给出了一个完美的结果:

(defmethod test-dummy java.lang.String [some-arg] "FOO!") 

在参数名称之前键入〜'以使其评估为符号作品,但发生了什么?

+1

也许你可以使用autogensym来避免这种情况。在宏定义中将'some-arg'改为'some-arg#'。 – coredump 2014-10-17 10:16:53

+0

好吧,但为什么会发生?为什么我从macroexpand获得正确的表达式? – user3026691 2014-10-17 12:31:39

+1

不幸的是,'macroexpand'函数与编译器不是100%兼容的。也就是说,我没有看到相同的宏扩展:'(.user/test-dummy clojure.core/addMethod java.lang.String(clojure.core/fn [user/some-arg]“FOO!”)) ' - 这个命名空间符合'some-arg',这是' – noisesmith 2014-10-17 14:08:14

好的。所以这里的问题是Clojure试图通过确保宏的扩展中没有符号可以从宏的扩展环境中捕获的不合格的本地人来制定macro hygiene

传统上,Lisp方言允许宏扩展包含任意符号。这会产生以下问题,其中包含要展开的宏的表达式定义了符号some-arg,其在宏的展开结果中没有定义地使用。这意味着这个宏正在从它的扩展环境中“捕获”一个符号/值,这是很少需要的行为。这正是Clojure编译器认为在这里与你的符号some-arg。 Clojure编译器尝试将some-arg解析为名称空间级别符号(以前的定义或需要为符号some-var创建别名),并且无法这样做,从而生成user/some-arg未定义的警告。

这个问题有两种解决方案。首先是使用宏扩展系统知道的some-arg表示本地并且不会尝试解析。

(defmacro silly [t] 
    `(defmethod test-dummy ~(resolve t) [some-arg#] "FOO!")) 

另一种方法是,你可以使用宏接头操作~插入引用符号的值。

(defmacro silly [t] 
    `(defmethod test-dummy ~(resolve t) [~'some-arg] "FOO!")) 

在这两种情况下,您必须在符号的所有用途中使用相同的表达式(gensym或splice)。顾名思义就是顾名思义会产生一个符号来使用,因此不会产生可重复的命名。这是用于转义符号冲突的功能。然而,拼接可以让你始终生成一个指定的符号,以防你需要一个真正的人类可用的名字(比如def),或者你确实想明确地从环境中关闭某些东西。

+0

啊!这就说得通了。谢谢。我错误地认为backtick的运行方式与Common Lisp相同。 – user3026691 2014-10-17 14:24:55