Try this:
(defmacro my-macro [& fns]
`(clojure.string/join (list ~@(map (fn [x] `(:foo (meta (var ~x)))) fns))))
(defn ^{:foo "bar"} my-func-1 [])
(defn ^{:foo "baz"} my-func-2 [])
(my-macro my-func-1 my-func-2) ;; => "barbaz"
How it Works
If you expand the macro you can start to see the parts in play.
(macroexpand '(my-macro my-func-1 my-func-2))
(clojure.string/join
(clojure.core/list (:foo (clojure.core/meta (var my-func-1)))
(:foo (clojure.core/meta (var my-func-2)))))
(var my-func-1)
Function metadata is stored on the var, so using (meta my-func-1)
is not sufficient. But, var
is a special form and does not compose like a normal function.
(fn [x] `(:foo (meta (var ~x))))
This anonymous function exists inside an escaped form, so it is processed inside the macro to produce the output forms. Internally it will create a the (:foo (meta (var my-func-1)))
form by first backtick escaping the outer form to declare it a literal, and not evaluated, list and then unescaping the x
var with a tilde to output the value instead of the symbol.
`(clojure.string/join (list ~@(map (fn [x] `(:foo (meta (var ~x))))
fns)))
This entire form is backtick escaped, so it will be returned literally. But, I still need to evaluate the map function generating the (:foo (meta (var my-func-1)))
form. In this case I have unescaped, and spliced (@
) the result of, the map
form directly. This first evaluates the map function and returns a list of generated forms, and then takes the contents of that list and splices it into the parent list.
(defmacro test1 [x] `(~x))
(defmacro test2 [x] `(~@x))
(macroexpand '(test1 (1 2 3))) ;; => ((1 2 3))
(macroexpand '(test2 (1 2 3))) ;; => (1 2 3)
You could also split out the map
function in a let
statement before hand for slightly more readability.
(defmacro my-macro [& fns]
(let [metacalls (map (fn [x] `(:foo (meta (var ~x)))) fns)]
`(clojure.string/join (list ~@metacalls))))