在我对Clojure For Comprehension example的回答中,我想删除一些重复内容:
(def all-letters (map char (range 65 90)))
(defn kw [& args] (keyword (apply str args)))
(concat
(for [l all-letters] (kw l))
(for [l all-letters l2 all-letters] (kw l l2))
(for [l all-letters l2 all-letters l3 all-letters] (kw l l2 l3)))
我想删除“for”重复。我写了以下宏:
(defmacro combine [times]
(let [symbols (repeatedly times gensym)
for-params (vec (interleave symbols (repeat 'all-letters)))]
`(for ~for-params (kw ~@symbols))))
适用于:
(concat (combine 1) (combine 2) (combine 3))
但如果我尝试:
(for [i (range 1 4)] (combine i))
我明白了:
CompilerException java.lang.ClassCastException: clojure.lang.Symbol cannot be cast to java.lang.Number, compiling:(NO_SOURCE_PATH:177)
发生了什么事?有没有更好的方法来删除重复?
答案 0 :(得分:1)
您的问题是combine
是一个在编译时扩展的宏。当您尝试展开符号i
时,它会失败,因为它的设计需要一定数量(次)。 i
在编译时只是一个符号,它只在运行时计算为数值。
我建议将combine
重写为函数而不是宏:你不需要宏,函数通常更方便(就像在这种情况下!)。
这是一个递归组合,可能大致与你想要的一样:
(defn combine
([times] (combine times nil))
([times letters]
(if (<= times 0)
(keyword (apply str letters))
(flatten (for [l all-letters] (combine (dec times) (cons l letters)))))))
答案 1 :(得分:1)
您可以修改宏,使concat
成为宏的一部分,如下所示。但我同意迈克拉的观点,认为最好有一个功能。
(defmacro combine [start end]
`(concat
~@(for [i (range start end)]
(let [symbols (repeatedly i gensym)
for-params (vec (interleave symbols (repeat 'all-letters)))]
`(for ~for-params (kw ~@symbols))))))
user=> (combine 1 2)
(:A :B :C :D :E :F :G :H :I :J :K :L :M :N :O :P :Q :R :S :T :U :V :W :X :Y)