我对 clojure 还是很陌生,但是我发现自己经常在其中使用的一个模式是这样的:我有一些集合,我想构建一个新集合,通常是一个哈希映射,其中包含一些过滤器或条件。总有几种方法可以做到这一点:使用 loop
或使用 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]
`(...))
看着
for
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)))
我的具体问题:
最佳答案
您的 reduce
版本也是我基于问题陈述的第一种方法。我认为它既好又简单,我希望它能够很好地工作,特别是因为 for
将产生一个分 block 的序列 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非常适合进行基准测试,并且可以消除任何与性能相关的猜测。
关于loops - Clojure:使用 `for` 绑定(bind)构建集合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33694209/