令人困惑的 clojure 宏 var 评估

Confusing clojure macro var evaluation

我正在尝试编写一个将 clojure 关键字转换为 java 枚举的 clojure 宏。但是在宏中对参数的评估令人困惑:

user=> (defmacro show-and-tell [thing]
#_=>   `(vector ~(name thing) ~(type thing) ~thing))
#'user/show-and-tell
user=> (macroexpand-1 (show-and-tell :foo))
["foo" clojure.lang.Keyword :foo]
user=> (def foo :bar)
#'user/foo
user=> (name foo)
"bar"
user=> (type foo)
clojure.lang.Keyword
user=> (macroexpand-1 (show-and-tell foo))
["foo" clojure.lang.Symbol :bar]

因此,如果直接将关键字作为参数提供,它会像我预期的那样工作。但是如果它是一个 var,我没有得到正确的 nametype

我可以使用 eval 使其 'sort-of-almost' 工作:

user=> (defmacro show-and-tell-with-eval [thing]
  #_=>   `(vector ~(name (eval thing)) ~(type (eval thing)) ~(eval thing)))
#'user/show-and-tell-with-eval
user=> (macroexpand-1 '(show-and-tell-with-eval foo))
(clojure.core/vector "bar" clojure.lang.Keyword :bar)
user=> (let [baz :bar-foo] (macroexpand-1 '(show-and-tell baz)))
(clojure.core/vector "baz" clojure.lang.Symbol baz)
user=> (let [baz :bar-foo] (macroexpand-1 '(show-and-tell-with-eval baz)))
CompilerException java.lang.RuntimeException: Unable to resolve symbol: baz in this context

谁能给我解释一下?有没有办法在宏中查看(本地)var 的 name

你可能想写

(defmacro show-and-tell [thing] `(vector (name ~thing) (type ~thing) ~thing))

临时说明:

了解正在发生的事情的关键是要知道何时 评估参数。宏将未评估的数据结构作为参数和 return 数据结构,然后使用上述规则对其进行评估。使用 ~ 你告诉编译器应该在运行时计算哪些数据结构,因此,你的 thing 参数,而不是 (name thing) 值的 return 值作为 thing 在后一种情况下,值将在编译时绑定,这不是你想要的

这里有关于写宏的进一步说明http://www.braveclojure.com/writing-macros/

您似乎对变量之间的关系及其包含的内容以及宏如何发挥作用感到困惑。 "Vars provide a mechanism to refer to a mutable storage location"(参见本例中的 offical docs on Vars). When you evaluate foo in the REPL, Clojure will evaluate it according to the rules outlined in the offical docs for evaluation,它 将符号 解析为 "the value of the binding of the global var named by the symbol"。

现在,了解宏 "are functions that manipulate forms, allowing for syntactic abstraction" 至关重要。基本上,宏允许直接访问传递给宏的任何参数,然后根据需要操作和评估相关数据。让我们看一下您的宏以及 "erroneous" 案例中发生的情况:

(defmacro show-and-tell [thing]
  `(vector ~(name thing) ~(type thing) ~thing))

您的宏定义得到一些 thing(无论 show-and-tell 的参数是什么)。此时,thing解析。只有在你的宏定义中你才有一些评估。请注意,在此调用中,您在 (show-and-tell foo) 的(评估的)结果 上调用 macroexpand-1,这可能不是您想要的:

user=> (macroexpand-1 (show-and-tell foo))
["foo" clojure.lang.Symbol :bar]

引用电话显示发生了什么:

user=> (macroexpand-1 '(show-and-tell foo))
(clojure.core/vector "foo" clojure.lang.Symbol foo)

您使用 "foo" 调用 vector(即 fooname),其中 foo 是一个符号,然后您的代码将解析foo 通常(并给出 :bar)。

根据您的描述,您似乎希望对所有参数进行正常的符号解析。 如果这是您想要的,那么您首先不需要宏。但仅作记录,现在您需要做的应该很明显:您需要首先评估 thing(这与您对 eval 所做的差不多)。换句话说,您将 unquote 运算符放错了:

(defmacro show-and-tell [thing]
  `(vector (name ~thing) (type ~thing) ~thing))
user=> (macroexpand-1 '(show-and-tell foo))
(clojure.core/vector (clojure.core/name foo) (clojure.core/type foo) foo)
user=> (show-and-tell foo)
["bar" clojure.lang.Keyword :bar]