2024年Clojure状况调查!中分享您的想法。

欢迎!请参阅关于页面了解有关其工作的更多信息。

0
序列

嗨,

我听说如果早在语言中加入transducers,它们就会用作所有序列惰性操作的构建块。由于它们是在后来添加的,您需要适当地修改代码以使用transducers。我想知道为什么不能使用eduction?我希望了解在什么情况下定义这种序列惰性操作并没有性能优势(当然,假设有相同的行为)

(defn map
  ([f] ;; the standard transducer definition
   ,,,)
  ([f coll]
   (eduction (map f) coll))

我试图弄清楚在假设的(可能是一个错误的假设)下

(->> (range 5000000)
     (eduction (map inc))
     (eduction (filter odd?))
     (eduction (map dec))
     (eduction (filter even?))
     (eduction (map (fn [n] (+ 3 n))))
     (eduction (filter odd?))
     (eduction (map inc))
     (eduction (filter odd?))
     (eduction (map dec))
     (eduction (filter even?))
     (eduction (map (fn [n] (+ 3 n))))
     (eduction (filter odd?))
     (into []))

等同于

(->> (range 5000000)
     (map inc)
     (filter odd?)
     (map dec)
     (filter even?)
     (map (fn [n] (+ 3 n)))
     (filter odd?)
     (map inc)
     (filter odd?)
     (map dec)
     (filter even?)
     (map (fn [n] (+ 3 n)))
     (filter odd?)
     (into []))

1 答案

+3

此示例假设使用所有结果。eductions不是序列,延迟计算但不缓存(从文档字符串:“请注意,这些应用每次在调用reduce/iterator时都会执行”),而且不是惰性的,因此具有很多含义(对于并发以及何时完成工作和消耗内存)。

如果您只想完成所需的工作量,那么eductions或reduce不是很好的选项,因为它们会完成所有工作。具有transducer的sequence函数可以提供增量计算的某些方面,但面对扩展transducer(如mapcat)时不如序列惰性。

很难想象如果transducer先出现的可能的一系列设计后果。也许Clojure会更偏爱reduce并优先考虑将输出到惰性替代方案中。

by
编辑 by
> 如果只想做必要的工作,eductions 或 reduce 并非最佳选择,因为它们会完成所有工作

我印象中 eductions 只会完成必要的工作(或者说,'由 eduction 的消费者请求' 的工作)

(let [ed (->> (range 5000000)
              (eduction (map #(doto % println)))
             (eduction (map #(doto % (-> (+ 100) println)))))]
  (println "taking")

  (into [] (take 3) ed)

  (println "taken")
taking
0
100
1
101
2
102
taken
=> nil

我在这里有什么明显的问题没有注意到吗,Alex?
by
Slack 上 Alex 的澄清

> 我在那句话中确实指的是 reduce
> [eduction] 是延迟的
> 它常用于饥饿场景
> 它基于迭代器,但常与reduce(一个饥饿过程)一起使用

更多细节请参阅https://clojurians.slack.com/archives/C03S1KBA2/p1681394119498109?thread_ts=1681383034.191669&cid=C03S1KBA2的线程
...