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

时间:2014-10-17 09:32:24

标签: methods macros clojure

这是一个展示问题的简短代码段:

(defmulti test-dummy type)

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

(silly String)

评估此结果"不能使用限定名称作为参数:user / some-arg",但运行macroexpand会产生非常好的结果:

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

打字〜'在参数名称之前使其评估成符号起作用,但是发生了什么?

1 个答案:

答案 0 :(得分:4)

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

传统上,Lisp方言允许宏扩展包含任意符号。这会产生一些问题,其中包含要扩展的宏的表达式定义了一个符号some-arg,该符号在宏的扩展结果中没有定义使用。这意味着宏是"捕获"来自其扩展环境的符号/值,这是很少需要的行为。这正是Clojure编译器认为你的符号some-arg所发生的事情。 Clojure编译器尝试将some-arg解析为命名空间级别符号(先前的定义或需要为符号some-var创建别名),但它无法执行此操作,从而生成user/some-arg未定义的警告

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

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

另一种方法是您可以使用宏拼接运算符~插入带引号的值。

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

在这两种情况下,您必须在符号的所有使用中使用相同的表达式(gensym或splice)。 gensym将顾名思义生成一个符号供使用,因此不会产生可重复的命名。这是逃避符号冲突的功能。然而,拼接将使您能够始终生成指定的符号,以防您需要一个真正的人类可用名称(例如def),或者您确实希望明确地关闭环境中的某些内容。