Macros Clojure宏抛出“;CompilerException java.lang.IllegalStateException:Var clojure.core/unquote未绑定";被叫的时候

Macros Clojure宏抛出“;CompilerException java.lang.IllegalStateException:Var clojure.core/unquote未绑定";被叫的时候,macros,functional-programming,clojure,lisp,Macros,Functional Programming,Clojure,Lisp,我正在尝试编写一个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

我正在尝试编写一个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)>
#
有人能帮我理解这里的语法引用是如何工作的吗?

之所以
`(~f~@(map~s~symbols))
不起作用,是因为编译器阻塞了
中不必要的
unquote
~
)。
unquote拼接
unquote外部的
syntax quote
`
),因此内部的两个
unquote
没有任何匹配的
syntax quote
,这就是为什么会出现“unbound”错误

您要做的是首先计算
(映射符号)
以获得操作数序列,然后将展平结果传递给函数(
~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和关键字都作为编译时文本提供时,宏版本才会起作用

(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

谢谢你的建议!,但是你能解释一下为什么把
~@
放在
map
之前会导致编译器出现问题吗?我原以为可以通过将map的结果拼接到列表表达式中来生成列表表达式,但我认为在这种情况下,
apply
的效果会更好。@Scott:经过一番思考,我想出了原本应该有的解决方案。希望现在更清楚一点。如果我的答案有帮助,您可以将其升级或标记为已接受。严格来说,应该是(带有参数[s f&symbols]`(~f~@(map(fn[symbol](list symbol s))symbols的defmacro)因为这将允许在运行时对map/struct求值。好吧,我现在明白你的意思了。我会投票支持你的答案,但我没有足够的声誉:/@cool Danger,Danger,你现在让调用方的代码对每个符号参数求值
s
一次。如果
s
的构造很昂贵或有副作用,这将是c例如,像
(带参数(do(println“computing…”){:x1:y2})(fn[ab](+ab)):y:x)
这样的东西会打印
“computing…”
两次。+1非常好的观点。最初的问题是关于宏的,但正如我们经常看到的那样,有时最好不要问这个问题。