在 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。我怀疑您拥有的 fun1
和 funA
函数仍在您的 REPL 中,因为您首先评估了 (item-defn "fun1")
和 (item-defn "funA")
。在我的盒子上我得到:
(map #(item-defn %) ["fun1" "funA"])
;=> (#'user/p1__22185# #'user/p1__22185#)
所以没有用名称 fun1
或 funA
定义函数。问题是 map
是一个函数而 item-defn
是一个宏。 map
epxression 中发生的事情是 item-defn
在编译时得到宏扩展,此时带有函数名称的字符串不可见。 macroexpander 无法知道您想要使用 "fun1"
作为您要被 defn
编辑的函数的名称。相反,macroexpander 只看到 %
,然后使用 gen-symed 名称作为 defn
-ed 函数的名称。 map
表达式在运行时计算,但宏扩展函数对提供的字符串执行任何操作都为时已晚。
Leonid 的解决方案有效,因为他使用另一个宏来迭代函数名称。这样迭代也发生在编译时。你看,宏是有传染性的。一旦开始,就无法停止。
我从以下代码开始(想象不止于此,但我认为这说明了重点):
(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。我怀疑您拥有的 fun1
和 funA
函数仍在您的 REPL 中,因为您首先评估了 (item-defn "fun1")
和 (item-defn "funA")
。在我的盒子上我得到:
(map #(item-defn %) ["fun1" "funA"])
;=> (#'user/p1__22185# #'user/p1__22185#)
所以没有用名称 fun1
或 funA
定义函数。问题是 map
是一个函数而 item-defn
是一个宏。 map
epxression 中发生的事情是 item-defn
在编译时得到宏扩展,此时带有函数名称的字符串不可见。 macroexpander 无法知道您想要使用 "fun1"
作为您要被 defn
编辑的函数的名称。相反,macroexpander 只看到 %
,然后使用 gen-symed 名称作为 defn
-ed 函数的名称。 map
表达式在运行时计算,但宏扩展函数对提供的字符串执行任何操作都为时已晚。
Leonid 的解决方案有效,因为他使用另一个宏来迭代函数名称。这样迭代也发生在编译时。你看,宏是有传染性的。一旦开始,就无法停止。