我正在尝试编写一个clojure宏,它允许我调用一个函数并使用提供的键值从map / struct中检索参数,例如:
(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)>
有人可以帮我理解语法引用在这里的作用吗?
答案 0 :(得分:4)
对于它的价值,无论如何这不应该是一个宏。一个函数非常强大,只有当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
答案 1 :(得分:3)
`(~f ~@(map ~s ~symbols))
不起作用的原因是编译器在unquote
(~
)内部对不必要的unquote-splicing
(~@
)进行了扼流。 unquote-splicing
取消引用外部syntax-quote
(`
),因此内部两个unquote
没有任何匹配的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