【问题标题】:Clojure macro throws "CompilerException java.lang.IllegalStateException: Var clojure.core/unquote is unbound" when calledClojure 宏在调用时抛出“CompilerException java.lang.IllegalStateException: Var clojure.core/unquote is unbound”
【发布时间】:2011-11-14 05:07:16
【问题描述】:

我正在尝试编写一个 clojure 宏,它允许我调用一个函数并使用提供的键值从映射/结构中检索参数,例如:

   (with-params  {:apple 2 :banana 3 :cherry 7} + :apple :banana)
   ;; 5

但是当我尝试使用我写的宏时:

(defmacro with-params [s f & symbols]
  `(~f ~@(map ~s ~symbols)))

打电话

   (with-params  {:apple 2 :banana 3 :cherry 7} + :apple :banana)

给我

#<CompilerException java.lang.IllegalStateException: Var clojure.core/unquote is unbound. (NO_SOURCE_FILE:0)>

有人可以帮我理解语法引用在这里的工作原理吗?

【问题讨论】:

    标签: macros functional-programming clojure lisp


    【解决方案1】:

    不管怎样,这不应该是一个宏。一个函数非常强大,而宏版本只有在映射和关键字都作为编译时文字给出时才能工作。

    (defmacro with-params-macro [s f & symbols]
      `(~f ~@(map s symbols)))
    
    (defn with-params-fn [s f & symbols]
      (apply f (map s symbols)))
    
    user> (with-params-macro {:x 1} (fn [z] z) :x)
    1
    user> (let [params {:x 1}] 
            (with-params-macro params (fn [z] z) :x))
    nil
    
    user> (let [params {:x 1}] 
            (with-params-fn params (fn [z] z) :x))
    1
    

    【讨论】:

    • +1 非常好的观点。最初的问题关于宏的,但正如我们在 SO 上经常看到的那样,有时最好不要问这个问题。
    【解决方案2】:

    `(~f ~@(map ~s ~symbols)) 不起作用的原因是编译器阻塞了unquote-splicing (~@) 中不必要的unquote (~)。 unquote-splicing 取消引用外部 syntax-quote (`),因此内部两个 unquotes 没有任何匹配的 syntax-quote,这就是您收到“未绑定”错误的原因。

    您要做的是首先评估(map s symbols) 以获取操作数序列,然后将展平结果传递给函数(~f);因此正确的版本是:

    (defmacro with-params [s f & symbols] `(~f ~@(map s symbols)))
    

    您可以通过以下方式轻松验证:

    (macroexpand '(with-params {:a 1 :b 2 :c 5} * :a :b :c))    ;; (* 1 2 5)
    (with-params {:a 1 :b 2 :c 5} * :a :b :c)                   ;; 10
    

    【讨论】:

    • 感谢您的建议!但您能否澄清一下为什么将~@ 放在map 之前会导致编译器问题?我认为我可以通过将 map 的结果拼接到其中来制作列表表达式,但我认为 apply 在这种情况下效果更好。
    • @Scott:经过一番思考,我想出了我原本应该有的解决方案。希望现在更清楚一点。如果我的回答有帮助,您可以点赞或标记为已接受。
    • 严格来说应该是 (defmacro with-params [sf & symbols] `(~f ~@(map (fn [symbol] (list symbol s)) symbols)) 因为这将允许映射/结构在运行时被评估。
    • 好的,我明白你的意思了。我会投票给你的答案,但我没有足够的声誉:/
    • @cool 危险,危险,你现在导致调用者的代码对每个符号参数评估一次s。如果s 的构造很昂贵或有副作用,这将是令人困惑和不愉快的。例如,(with-params (do (println "computing...") {:x 1 :y 2}) (fn [a b] (+ a b)) :y :x) 之类的内容将打印两次 "computing..."
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-13
    • 2012-10-04
    • 2018-03-07
    • 1970-01-01
    • 2017-12-19
    • 1970-01-01
    相关资源
    最近更新 更多