我仍然相当新的clojure,但我发现自己经常在其中使用的模式是这样的:我有一些集合,我想建立一个新的集合,通常是哈希映射,其中有一些过滤器或条件.总有几种方法可以做到这一点:例如,使用循环或使用reduce与map / filter相结合,但我想实现更像for宏的东西,它具有很好的语法来控制在循环中得到评估的内容.我想生成一个语法宏如下:
(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]
`(...))
看看clojure.core中的for代码,很明显我不想自己重新实现它的语法(甚至忽略了复制代码的普通危险),但在上面的宏中提出类似行为的是比我最初预期的要复杂得多.我最终想出了以下内容,但我觉得(a)这可能不是非常高效,而且(b)应该有一种更好的,仍然是clojure-y的方式来做到这一点:
(defmacro build
[sym init-val bindings expr]
`(loop [result# ~init-val, s# (seq (for ~bindings (fn [~sym] ~expr)))]
(if s#
(recur ((first s#) result#) (next s#))
result#))
;; or `(reduce #(%2 %1) ~init-val (for ~bindings (fn [~sym] ~expr)))
我的具体问题:
>是否有内置的clojure方法或库已经解决了这个问题,或许更优雅?
>那些更熟悉clojure性能的人能否让我知道这个实现是否有问题以及我是否应该担心性能是多少?假设我可能会经常使用这个宏用于相对较大的集合?
>有没有什么好的理由我应该在上面的宏的reduce版本上使用循环,反之亦然?
>任何人都可以看到更好的宏实现吗?
最佳答案 您的reduce版本也是我基于问题陈述的第一种方法.我认为它很好而且直截了当,我希望它能很好地工作,特别是因为for会产生一个减少的seq,它可以很快地迭代.
无论如何生成函数来生成输出,我不希望构建扩展引入的额外层特别成问题.基于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非常适合进行基准测试,并且可以消除任何与性能相关的猜测.