我还是相当新的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
我的具体问题:
答案 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}}