变压器与减速器的区别是什么? - Clojure

What separates a transformer from a reducer ? - Clojure

据我所知,转换器是使用函数来改变、改变元素集合。就像我确实为

集合中的每个元素添加了 1
[1 2 3 4 5]

变成了

[2 3 4 5 6]

但是为此编写代码看起来像

(map inc)

但我一直将这种代码与减速器混淆。因为它产生了一个新的累加结果。

我想问的是,变压器和减速器有什么区别?

您可能只是混淆了各种术语(正如上面的评论所暗示的),但我会通过自由解释您所说的减速机和变压器来回答我认为是您的问题。

减少:

reducing 函数(您可能认为是 reducer)是一个接受累加值和当前值的函数,returns 是一个新的累加值。

(accumulated, current) => accumulated

这些函数被传递给 reduce,它们依次执行一个序列,执行 reducing 函数的主体用它的两个参数(累积和当前)表示的任何内容,然后 return 计算一个新的累积值它将用作下一次调用归约函数的累加值(第一个参数)。

例如,plus 可以看作是一个约简函数。

(reduce + [0 1 2]) => 3

首先,使用 0 和 1 调用缩减函数(在本例中为 plus),returns 1。在下一次调用时,1 现在是累加值,2 是当前值,所以用 1 和 2 调用 plus,returning 3,这完成了缩减,因为集合中没有其他元素要处理。

查看 reduce 实现的简化版本可能会有所帮助:

(defn reduce1
  ([f coll] ;; f is a reducing function
      (let [[x y & xs] coll]
           ;; called with the accumulated value so far "x"
           ;; and cur value in input sequence "y"
           (if y (reduce1 f (cons (f x y) xs)) 
               x)))
  ([f start coll]
      (reduce1 f (cons start coll))))

您可以看到函数 "f" 或 "reducing function" 在每次迭代时都被调用,带有两个参数,即到目前为止的累加值和输入序列中的下一个值。此函数的 return 值用作下一次调用等的第一个参数,因此具有类型:

(x, y) => x

变换:

一个转换,我想你的意思是,表示输入的形状没有改变,只是根据任意函数进行了简单的修改。这将是您传递给 map 的函数,因为它们应用于每个元素并构建一个具有相同形状的新集合,但该函数应用于每个元素。

(map inc [0 1 2]) => '(1 2 3)

注意形状是一样的,它仍然是一个 3 元素序列,而在上面的归约中,你输入一个 3 元素序列并得到一个整数。减少可以改变最终结果的形状,地图不会。

请注意,我说 "shape" 不会改变,但每个元素的类型可能会根据您的 "transforming" 函数的作用而改变:

(map #(list (inc %)) [0 1 2]) => '((1) (2) (3))

它仍然是一个 3 元素序列,但现在每个元素都是一个列表,而不是一个整数。

附录:

Clojure 中有两个相关的概念,Reducers 和 Transducers,我只是想提一下,因为你询问了 reducers(在 Clojure 中具有特定含义)和 transformers(Clojurists 通常分配给转换的名称通过 shorthand "xf" 函数)。如果我试图在这里解释两者的细节,它会将这个已经很长的答案变成 short-story,而且它比其他人做得更好:

换能器: http://elbenshira.com/blog/understanding-transducers/ https://www.youtube.com/watch?v=6mTbuzafcII

减速器和传感器: https://eli.thegreenplace.net/2017/reducers-transducers-and-coreasync-in-clojure/

原来集合的很多变换都可以用reduce来表达。例如 map 可以实现为

(defn map [f coll] (reduce (fn [x y] (conj x (f y))) [] [0 1 2 3 4]))

然后你会打电话给

(map inc [1 2 3 4 5])

获得

[2 3 4 5 6]

在我们自制的 map 实现中,我们传递给 reduce 的函数是

(fn [x y] (conj x (f y))))

其中 f 是我们要应用于每个元素的函数。所以我们可以写一个函数,为我们生成这样一个函数,传递我们想要映射的函数。

(defn mapping-with-conj [f] (fn [x y] (conj x (f y))))

但是假设我们要向集合中添加元素,我们仍然会在上述函数中看到 conj 的存在。我们可以通过额外的间接访问获得更大的灵活性:

(defn mapping [f] (fn [step] (fn [x y] (step x (f y)))))

然后我们可以这样使用:

(def increase-by-1 (mapping inc))
(reduce (increase-by-1 conj) [] [1 2 3])

您提到的 (map inc) 执行我们对 (mapping inc) 的调用。你为什么要这样做?答案是它给了我们很大的灵活性来构建东西。例如,我们可以做

而不是建立一个集合
(reduce ((map inc) +) 0 [1 2 3 4 5])

这将为我们提供映射集合 [2 3 4 5 6] 的总和。或者我们可以通过简单的函数组合来添加额外的处理步骤。

(reduce ((comp (filter odd?) (map inc)) conj) [] [1 2 3 4 5])

这将在我们映射之前首先从集合中删除偶数元素。 Clojure 中的 transduce 函数基本上完成了上面一行所做的工作,但也处理了另外一些额外的细节。所以你实际上会写

(transduce (comp (filter odd?) (map inc)) conj [] [1 2 3 4 5])

总结,Clojure中的map函数有两个参数。像 (map inc [1 2 3 4 5]) 这样调用它会映射集合的每个元素,这样您就可以获得 [2 3 4 5 6]。像 (map inc) 一样调用它给了我们一个与上面解释中的 mapping 函数非常相似的函数。