减少“为”理解重复

时间:2012-07-17 23:04:17

标签: macros clojure code-duplication for-comprehension

在我对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) 

发生了什么事?有没有更好的方法来删除重复?

2 个答案:

答案 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)