3

取下面三个函数,分别在 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]它们都返回42。我知道前两个背后的机制是相似的,因为map在 Clojure 中是惰性的,但第三个使用了传感器。有人可以展示执行这些功能的中间步骤吗?

4

1 回答 1

4

对于这个具体的例子,第二个和第三个例子之间的中间步骤是相同的​​。这是因为mapfilter是作为序列到序列的惰性转换实现的,正如您毫无疑问已经知道的那样。

map 和 filter 的转换器版本使用与非转换器版本相同的基本功能定义,除了它们“conj”(或不,在 filter 的情况下)到结果流的方式在其他地方定义。确实,如果你看一下map的源代码,有显式的数据结构构造函数在使用,而transducer变体没有使用这样的函数——它们是通过传入的。在非transducer版本中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

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

于 2016-09-04T12:53:47.330 回答