是否有一种惯用的方法来干掉clojure中类似的函数定义?

时间:2015-09-21 15:12:12

标签: clojure macros dry

我从以下代码开始(想象不止这些,但我认为这得到了重点):

(defn fun1 [arg] {:fun1 arg})
(defn funA [arg] {:funA arg})
(defn funOne [arg] {:funOne arg})
(defn funBee [arg] {:funBee arg})

(defn -main [& args] (prn (fun1 "test-data")))

我的下一个传递就是这样:

(defmacro item-defn [a]
  `(defn ~(symbol a) [arg#] {~(keyword a) arg#}))
(item-defn "fun1")
(item-defn "funA")
(item-defn "funOne")
(item-defn "funBee")
(defn -main [& args] (prn (fun1 "test-data")))

有没有办法将其归结为:

(defmacro item-defn [a]
  `(defn ~(symbol a) [arg#] {~(keyword a) arg#}))
(map #(item-defn %) ["fun1" "funA" "funOne" "funBee"])
(defn -main [& args] (prn (fun1 "test-data")))

(我在repl中尝试过,它似乎有效,但是当我在其中加载一个clj文件时,它就不起作用。它给了我一个" CompilerException" "无法解析符号:fun1")

我是否误用了宏?你会怎么做?

3 个答案:

答案 0 :(得分:5)

您可以为此目的定义另一个宏,例如:

(defmacro item-defn [a]
  `(defn ~(symbol a) [arg#] {~(keyword a) arg#}))

(defmacro items-defn [& names]
  `(do ~@(for [n names] `(item-defn ~n))))

然后您就可以使用它来定义任意数量的函数:

(items-defn "fun1" "funA" "funOne" "funBee")

答案 1 :(得分:1)

我想知道你map表达式是否真的在REPL中起作用。我怀疑您的fun1funA函数仍然在您的REPL中,因为您首先评估了(item-defn "fun1")(item-defn "funA")。在我的盒子上,我得到:

(map #(item-defn %) ["fun1" "funA"])
;=> (#'user/p1__22185# #'user/p1__22185#)

因此,没有使用名称fun1funA定义任何功能。问题是map是一个函数,item-defn是一个宏。在map epxression中发生的事情是item-defn在编译时被宏扩展,此时具有函数名称的字符串不可见。宏扩展程序无法知道您希望使用"fun1"作为defn - ed函数的名称。相反,宏扩展器只看到%,然后使用gen-symed名称作为defn -ed函数的名称。 map表达式是在运行时计算的,但是宏扩展函数对提供的字符串执行任何操作都为时已晚。

Leonid的解决方案有效,因为他使用另一个宏来迭代函数名称。这样迭代也会在编译时发生。你看,宏有点具有传染性。一旦你开始,你就无法停止。

答案 2 :(得分:0)

在宏中,名称已经是符号,因此您可以执行以下操作:

(defmacro item-defn [name]
  `(defn ~name [arg#] {~(keyword name) arg#}))

然后

(item-defn fun1)