0

In Clojure, how do I make a library macro which processes supplied functions metadata and return some result? Amount of functions is unlimited and they should be passed without being boxed into a sequence ((my-macro fn1 fn2) instead of (my-macro [fn1 fn2]))

Say, we expect function vars having :foo keys in meta and the macro concatenates their values. The following snippet should work in REPL (considering my-macro is in the namespace):

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"

I tried several approaches but was only able to process single function so far.

Thanks!

4

1 回答 1

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 函数并返回生成的表单列表,然后获取该列表的内容并将其拼接到父列表中。

(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))))
于 2013-10-20T17:27:38.667 回答