Clojure:使用`for`绑定构建集合

时间:2015-11-13 13:47:49

标签: loops for-loop clojure macros reduce

我还是相当新的clojure,但我发现自己经常在其中使用的模式是这样的:我有一些集合,我想建立一个新的集合,通常是哈希映射,从中有一些过滤器或条件。总有几种方法可以执行此操作:例如,使用loop或使用reduce结合map / filter,但我希望实现更像{ {1}}宏,它具有很好的语法来控制在循环中得到评估的内容。我想生成一个语法如下的宏:

for

查看clojure.core中的(defmacro build "(build sym init-val [bindings...] expr) evaluates the given expression expr over the given bindings (treated identically to the bindings in a for macro); the first time expr is evaluated the given symbol sym is bound to the init-val and every subsequent time to the previous expr. The return value is the result of the final expr. In essence, the build macro is to the reduce function as the for macro is to the map function. Example: (build m {} [x (range 4), y (range 4) :when (not= x y)] (assoc m x (conj (get m x #{}) y))) ;; ==> {0 #{1 3 2}, 1 #{0 3 2}, 2 #{0 1 3}, 3 #{0 1 2}}" [sym init-val [& bindings] expr] `(...)) 代码,很明显我不想自己重新实现它的语法(甚至忽略了复制代码的普通危险),但是想出来 - 像上面的宏中的行为比我最初预期的要复杂得多。我最终想出了以下内容,但我觉得(a)这可能并不是非常高效,而且(b)应该有一种更好的,仍然是背叛的方式来做到这一点:

for

我的具体问题:

  1. 是否有内置的clojure方法或库已经解决了这个问题,或许更优雅?
  2. 那些更熟悉clojure性能的人能否让我知道这个实现是否有问题以及我是否应该担心性能有多少/假设我可以非常频繁地使用这个宏用于相对较大的集合?
  3. 有没有什么好的理由我应该在上面的宏的reduce版本上使用循环,反之亦然?
  4. 任何人都可以看到更好的宏实现吗?

2 个答案:

答案 0 :(得分:1)

您的reduce版本也是我的第一个基于问题陈述的方法。我觉得它很好,很直接,我希望它能很好地工作,特别是因为for会产生一个reduce能够快速迭代的分块序列。

无论如何,

for生成了输出生成的函数,我不希望build扩展引入的额外层特别成问题。基于volatile!对此版本进行基准测试可能仍然值得:

(defmacro build [sym init-val bindings expr]
  `(let [box# (volatile! ~init-val)] ; AtomicReference would also work
     (doseq ~bindings
       (vreset! box# (let [~sym @box#] ~expr)))
     @box#))

Criterium非常适合进行基准测试,并且可以消除任何与性能相关的猜测。

答案 1 :(得分:0)

我不想完全接受你的文档字符串的示例代码,因为它不是惯用的clojure。但是,如果采用plumbing.core's for-map,您可以提出类似的for-map-update

(defn update!
  "Like update but for transients."
  ([m k f] (assoc! m k (f (get m k))))
  ([m k f x1] (assoc! m k (f (get m k) x1)))
  ([m k f x1 x2] (assoc! m k (f (get m k) x1 x2)))
  ([m k f x1 x2 & xs] (assoc! m k (apply f (get m k) x1 x2 xs))))

(defmacro for-map-update
  "Like 'for-map' for building maps but accepts a function as the value to build map values."
  ([seq-exprs key-expr val-expr]
   `(for-map-update ~(gensym "m") ~seq-exprs ~key-expr ~val-expr))
  ([m-sym seq-exprs key-expr val-expr]
   `(let [m-atom# (atom (transient {}))]
      (doseq ~seq-exprs
        (let [~m-sym @m-atom#]
          (reset! m-atom# (update! ~m-sym ~key-expr ~val-expr))))
      (persistent! @m-atom#))))

(for-map-update
  [x (range 4)
   y (range 4)
   :when (not= x y)]
  x (fnil #(conj % y) #{} ))
;; => {0 #{1 3 2}, 1 #{0 3 2}, 2 #{0 1 3}, 3 #{0 1 2}}