令人困惑的clojure宏变量评估

时间:2015-01-20 08:40:08

标签: macros clojure

我正在尝试编写一个将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来实现“几乎”的工作:

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

2 个答案:

答案 0 :(得分:2)

你可能想写

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

临时解释:

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

这里有关于编写宏http://www.braveclojure.com/writing-macros/

的进一步说明

答案 1 :(得分:1)

您似乎对变量与它们包含的内容之间的关系以及宏如何发挥作用感到困惑。 " Vars提供了一种引用可变存储位置的机制。 (参见offical docs on Vars)。当您在REPL中评估foo时,Clojure将根据offical docs for evaluation中列出的规则对其进行评估,在这种情况下,它将解析符号到"由符号"。

命名的全局变量的绑定值

现在,理解宏"是操纵表单的函数,允许语法抽象"是至关重要的。基本上,宏允许直接访问传递给宏的任何参数,然后根据需要操作和评估相关数据。让我们一瞥你的宏,以及你的错误"情况下:

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

您的宏定义会被提供一些thing(无论show-and-tell的参数是什么)。此时,thing得到解决。只有在您的宏定义中,您才有一些评估。请注意,在此次通话中,您在[{1}}的(已评估的)结果上调用macroexpand-1,这可能不是您想要的:

(show-and-tell foo)

引用电话会显示正在进行的操作:

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

您正在使用" foo"来呼叫user=> (macroexpand-1 '(show-and-tell foo)) (clojure.core/vector "foo" clojure.lang.Symbol foo) (即vector的{​​{1}},其中name是符号,然后您的代码将正常解析foo(并提供foo)。

根据您的描述,您似乎希望所有参数都能正常进行符号解析。 如果这是你想要的,你首先不需要一个宏。但仅仅是为了记录,现在你应该明白你需要做什么:你需要评估首先foo(这与您对:bar所做的非常相似)。换句话说,您将thing运算符设置错误:

eval