4
votes

I'm trying to write a clojure macro that lets me call a function and retrieve the arguments from a map/struct using the provided key values such as:

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

but when I try to use the macro that I wrote:

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

calling

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

gives me

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

Could someone help me understand how syntax-quoting works here?

2

2 Answers

4
votes

For what it's worth, this shouldn't be a macro anyway. A function is plenty powerful, and the macro version will only work if both the map and the keywords are given as compile-time literals.

(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
3
votes

The reason that `(~f ~@(map ~s ~symbols)) doesn't work is that the compiler chokes on the unnecessary unquote (~) inside the unquote-splicing (~@). The unquote-splicing unquotes the outer syntax-quote (`), so the inner two unquotes don't have any matching syntax-quote, which is why you were getting the "unbound" error.

What you want to do is evaluate (map s symbols) first to get the sequence of operands, then pass the flattened result to the function (~f); therefore the correct version is:

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

You can easily verify this with:

(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