【问题标题】:Strange error when generating method with a macro?使用宏生成方法时出现奇怪的错误?
【发布时间】:2014-10-17 09:32:24
【问题描述】:

这是一个演示问题的简短 sn-p:

(defmulti test-dummy type)

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

(silly String)

评估此结果会导致“无法使用限定名称作为参数:user/some-arg”,但运行宏扩展会得到非常好的结果:

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

在参数名称之前键入 ~' 以使其评估为符号有效,但这是怎么回事?

【问题讨论】:

  • 也许你可以使用 autogensym 来避免这种情况。将宏定义中的 some-arg 更改为 some-arg#
  • 好的,但是为什么会这样呢?为什么我会从 macroexpand 中得到正确的表达式?
  • 遗憾的是,macroexpand 函数与编译器并非 100% 兼容。也就是说,我没有看到与您相同的宏扩展:(. user/test-dummy clojure.core/addMethod java.lang.String (clojure.core/fn [user/some-arg] "FOO!")) - 此命名空间符合 some-arg,这是 ` 的预期和记录行为
  • @noisesmith:你是对的。用于 emacs 的 CIDER REPL 似乎存在问题。文档指出 C-c C-m 调用 macroexpand-1,但手动调用它会返回不同的结果,与您得到的结果相同。我想这两个谜团都解决了。
  • 我不再对苹果酒中的任何奇怪现象感到惊讶。

标签: methods macros clojure


【解决方案1】:

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

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

这个问题有两种规范的解决方案。第一种是对some-arg 使用 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),或者您确实想明确地从环境中关闭某些东西。

【讨论】:

  • 啊!这就说得通了。谢谢你。我错误地认为反引号的工作方式与 Common Lisp 中的相同。
猜你喜欢
  • 2014-12-09
  • 1970-01-01
  • 2020-09-09
  • 1970-01-01
  • 2020-08-30
  • 2013-05-05
  • 1970-01-01
  • 1970-01-01
  • 2023-03-21
相关资源
最近更新 更多