Clojure宏处理多个功能元数据

时间:2013-10-20 12:35:44

标签: macros clojure metadata defn

在Clojure中,如何创建一个库宏来处理提供的函数元数据并返回一些结果?函数的数量是无限的,它们应该被传递而不被加框成序列((my-macro fn1 fn2)而不是(my-macro [fn1 fn2])

说,我们期望函数变量具有:meta中的foo键,宏连接它们的值。以下代码段应该在REPL中工作(考虑my-macro在命名空间中):

user=> (defn my-func-1 {:foo "bar"} [])
(defn my-func-1 {:foo "bar"} [])
#'user/my-func-1
user=> (defn my-func-2 {:foo "baz"} [])
(defn my-func-2 {:foo "baz"} [])
#'user/my-func-2
user=> (my-macro my-func-1 my-func2)
(my-macro my-func-1 my-func2)
"barbaz"

我尝试了几种方法,但到目前为止只能处理单一功能。

谢谢!

1 个答案:

答案 0 :(得分:2)

试试这个:

(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)

您还可以事先在map声明中拆分let函数,以提高可读性。

(defmacro my-macro [& fns]
  (let [metacalls (map (fn [x] `(:foo (meta (var ~x)))) fns)]
    `(clojure.string/join (list ~@metacalls))))