试试这个:
(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"
工作原理
如果您展开宏,您可以开始看到正在运行的部分。
(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)
函数元数据存储在 var 中,因此使用 (meta my-func-1) 是不够的。但是,var 是一种特殊形式,不像普通函数那样组成。
(fn [x] `(:foo (meta (var ~x))))
此匿名函数存在于转义形式中,因此在宏内部对其进行处理以生成输出形式。在内部,它将创建一个 (:foo (meta (var my-func-1))) 表单,首先反引号转义外部表单以将其声明为文字而不是评估的列表,然后使用波浪号取消转义 x var 以输出值而不是符号。
`(clojure.string/join (list ~@(map (fn [x] `(:foo (meta (var ~x))))
fns)))
整个表单都是反引号转义的,所以它会按字面意思返回。但是,我仍然需要评估生成 (:foo (meta (var my-func-1))) 表单的 map 函数。在这种情况下,我直接对map 表单的结果进行了转义和拼接(@)。这首先评估 map 函数并返回生成的表单列表,然后获取该列表的内容并将其拼接到父列表中。
(defmacro test1 [x] `(~x))
(defmacro test2 [x] `(~@x))
(macroexpand '(test1 (1 2 3))) ;; => ((1 2 3))
(macroexpand '(test2 (1 2 3))) ;; => (1 2 3)
您也可以事先在 let 语句中拆分 map 函数以提高可读性。
(defmacro my-macro [& fns]
(let [metacalls (map (fn [x] `(:foo (meta (var ~x)))) fns)]
`(clojure.string/join (list ~@metacalls))))