【发布时间】:2014-12-30 10:16:22
【问题描述】:
假设我想处理一个序列,其中处理是有状态的,并且我想使用 Clojure 序列库以一种惰性的方式执行此操作。
作为一个具体的例子,假设我想实现distinct,它自然地实现为一个有状态的过滤器,跟踪看到的元素。我的第一个尝试是不使用序列库,而是使用lazy-seq:
(defn distinct' [coll]
(let [process (fn process [coll seen]
(lazy-seq
(when-let [[x & r] (seq coll)]
(if (contains? seen x)
(process r seen)
(cons x (process r (conj seen x)))))))]
(process coll #{})))
我对上述内容没有意见,但是,如果可以的话,我更愿意使用map 和filter 之类的方法。我正在努力解决如何干净地做到这一点。一种可行的方法是使用reductions:
(defn distinct'' [coll]
(->> (reductions (fn [[_ _ seen] x]
(if (contains? seen x)
[false nil seen]
[true x (conj seen x)]))
[false nil #{}]
coll)
(filter first)
(map second)))
从根本上说,distinct'' 实现了使用更高级别抽象(map、filter、reductions)的目标,同时保持惰性。但它并没有过于复杂,尤其是我将它作为累加器传递的向量。
如果我尝试将状态直接嵌入到要与filter 一起使用的谓词中,虽然它更接近我的想象,但它似乎是“错误的”,我什至都不好意思写以下代码(filter 的文档甚至说谓词应该没有副作用):
(defn distinct''' [coll]
(let [seen (atom #{})]
(filter (fn [x]
(if (contains? @seen x)
false
(do (swap! seen conj x)
true)))
coll)))
我的问题:
有没有办法以干净的方式使用序列库进行这样的惰性状态处理(即使对于这个distinct 示例)?或者,lazy-seq 通常是最干净的方法吗?
【问题讨论】:
-
似乎是见仁见智。我认为惰性序列方法更清晰。
-
我的 0.02$:我不认为最后一个是错的,我不理解将 internal 状态变异为 side effects 并且它是一个非常易读的实现,但同意这是一个意见问题
-
顺便说一句,在从另一个惰性序列生成惰性序列时,您应该避免使用
[x & xs]解构,因为它会强制输入序列中的 两个 元素,以便找出xs是否应该为零,如果xs的生产成本很高,这可能会很糟糕。对于库质量代码,您必须使用first和rest。 -
好点!顺便说一句:我对拥有一个最容易推理正确性的“参考实现”的概念以及优化的“生产”或“库”实现的概念很感兴趣,也许与通过@的参考 impl 进行比较987654342@。我发现
distinct的发货实现很吸引人,它的内部loop/recur在寻找新元素时避免了lazy-seq。
标签: clojure