【问题标题】:Confusing clojure macro var evaluation令人困惑的 clojure 宏 var 评估
【发布时间】:2015-01-20 08:40:08
【问题描述】:

我正在尝试编写一个将 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

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

【问题讨论】:

    标签: macros clojure


    【解决方案1】:

    你可能想写

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

    临时解释:

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

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

    【讨论】:

    • 啊,幸运的是我们简单的头脑会犯简单的错误。谢谢,那当然解决了。
    【解决方案2】:

    您似乎对变量之间的关系及其包含的内容以及宏如何发挥作用感到困惑。 “Var 提供了一种机制来引用可变存储位置”(参见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不会得到解决。只有在您的宏定义中,您才有一些评估。请注意,在此通话中,您在 (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)。

    根据您的描述,您似乎期望所有参数都会进行正常的符号解析。 如果这是你想要的,你一开始就不需要宏。但为了记录,现在你需要做的事情应该很明显:你需要评估 @987654338 @ first(这几乎就是您对 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]
    

    【讨论】:

      猜你喜欢
      • 2010-10-01
      • 2012-11-19
      • 1970-01-01
      • 1970-01-01
      • 2022-08-16
      • 1970-01-01
      • 2011-03-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多