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

Is there an idiomatic way to dry up similar function definitions in clojure?

我从以下代码开始(想象不止于此,但我认为这说明了重点):

(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" "Unable to resolve symbol: fun1")

我是在滥用宏吗?你会怎么做?

在您的宏中,名称已经是一个符号,因此您可以:

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

然后

(item-defn fun1)

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

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

我想知道你的 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 在编译时得到宏扩展,此时带有函数名称的字符串不可见。 macroexpander 无法知道您想要使用 "fun1" 作为您要被 defn 编辑的函数的名称。相反,macroexpander 只看到 %,然后使用 gen-symed 名称作为 defn-ed 函数的名称。 map 表达式在运行时计算,但宏扩展函数对提供的字符串执行任何操作都为时已晚。

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