【问题标题】:Clojure macro to process multiple function metadataClojure 宏处理多个函数元数据
【发布时间】:2013-10-20 12:35:43
【问题描述】:

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

比如说,我们希望函数 vars 在 meta 中有 :foo 键,而宏将它们的值连接起来。以下 sn-p 应该在 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"

我尝试了几种方法,但目前只能处理单个函数。

谢谢!

【问题讨论】:

    标签: macros clojure metadata defn


    【解决方案1】:

    试试这个:

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

    【讨论】:

    • 只是为了好奇,你为什么将一个空字符串传递给clojure.string/join
    • 修复了删除它的答案。在编写宏时,我有一个分隔符,但我忘了删除参数。感谢您的评论。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-12
    • 1970-01-01
    • 1970-01-01
    • 2011-07-28
    • 2011-08-09
    相关资源
    最近更新 更多