这些用柯里化和转换器实现的函数有什么区别?

What's the difference between these functions implemented with currying and transducers?

取下面三个函数,分别在Haskell和Clojure中实现:

f :: [Int] -> Int
f = foldl1 (+) . map (*7) . filter even

(defn f [coll]
  ((comp
    (partial reduce +)
    (partial map #(* 7 %)
    (partial filter even?)) coll))

(defn f [coll]
  (transduce
    (comp
      (filter even?)
      (map #(* 7 %)))
     + coll))

当它们应用于像 [1, 2, 3, 4, 5] 这样的列表时,它们都会 return 42。我知道前两个背后的机制是相似的,因为 map 在 Clojure 中是惰性的,但第三个使用转换器。有人可以显示执行这些功能的中间步骤吗?

第二个和第三个示例之间的中间步骤与此特定示例相同。这是因为 mapfilter 被实现为序列到序列的惰性转换,因为你已经毫无疑问了意识到。

map 和 filter 的换能器版本使用与非换能器版本相同的基本功能定义,除了它们“conj”的方式(或不,在过滤器的情况)到结果流的定义在别处。事实上,如果你查看地图的源代码,explicit data-structure construction functions in use, whereas the transducer variant 没有使用这样的函数——它们是通过 rf 传入的。显式使用 cons 在非传感器版本中意味着他们总是要处理序列

IMO,使用转换器的主要好处是您可以定义您正在做的 过程 ,而不是将使用您的过程的东西。因此,对您的第三个示例进行更有趣的重写可能是:

(def process (comp (filter even)
                   (map #(* 7 %))))

(defn f [coll] (transduce process + collection))

应用程序作者决定何时需要这种抽象是一种练习,但它绝对可以为重用打开机会。


您可能会想到,您可以简单地重写

(defn process [coll]
  ((comp
    (partial map #(* 7 %)
    (partial filter even?)) coll))

(reduce + (process coll))

同样的效果;这是真实的。 当您的输入始终是一个序列(或始终是同一种流/您知道它将是哪种流)时,可以说没有充分的理由创建转换器。但是这里可以证明重用的力量(假设进程是一个转换器)

(chan 1 process)  ;; an async channel which runs process on all inputs

(into [] process coll)  ;; writing to a vector

(transduce + process coll)  ;; your goal

transducers 背后的动机本质上是为了不再需要为不同的集合类型编写新的集合函数。 Rich Hickey 提到了他在核心异步库中编写诸如 map mapcat 等函数的挫败感 -- what map 和 mapcat are, 已经定义了,但是因为它们 假设 它们对序列进行操作(我在上面链接的明确 cons),所以它们不能应用于异步通道。但是通道可以在转换器版本中提供自己的 rf 以让它们重用这些功能。