这是一个展示问题的简短代码段:
(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!")
打字〜'在参数名称之前使其评估成符号起作用,但是发生了什么?
答案 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),或者您确实希望明确地关闭环境中的某些内容。