【发布时间】:2016-02-15 02:54:34
【问题描述】:
我对 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]
`(...))
查看 clojure.core 中的 for 代码,很明显我不想自己重新实现它的语法(甚至忽略复制代码的普通危险),而是想出类似 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 版本上使用循环,反之亦然?
- 谁能看到更好的宏实现?
【问题讨论】:
标签: loops for-loop clojure macros reduce