在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"
我尝试了几种方法,但到目前为止只能处理单一功能。
谢谢!
答案 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))))