【问题标题】:Clojure: precise definition of reducerClojure:reducer 的精确定义
【发布时间】:2016-06-27 07:05:52
【问题描述】:

Clojure website 定义了一个 reducer 如下:

reducer 是可归约集合(知道如何归约自身的集合)与归约函数(归约过程中需要完成的“配方”)的组合。

以下是reducer函数的实现(来自Rich的blog post on the topic

(defn reducer
   ([coll xf]
    (reify
     clojure.core.protocols/CollReduce
     (coll-reduce [_ f1 init]
       (clojure.core.protocols/coll-reduce coll (xf f1) init)))))

似乎更准确地说reducer是一个reducible collection和一个reducing function transformer(后来称为transducer)的组合,而不是一个减少函数

reducer 不“知道”任何关于由 reduce 提供的归约函数。它所知道的只是一个“配方”,用于获取一些归约函数并修改(转换)它。

我的理解是对“reducer”的正确定义吗?还是我对“具有归约函数的可归约集合”定义遗漏了什么?

【问题讨论】:

    标签: clojure reducers


    【解决方案1】:

    文档中的语言混淆了减速器和转换器。转换器可以被认为是简化器的优化实现,其目标是减少对象分配(和后续 GC)。这种优化不会改变 reducer 的概念模型。

    所以,坚持简单的reduce,它的目标是简单地提供一种从序列中累积结果的方法。最简单的例子是对一个序列求和:

    (ns clj.core
      (:require [clojure.string :as str] )
      (:use tupelo.core)) ; it->
    
    (def values (range 6))
    (spyx values)
    
    (def total (reduce + 0 values))
    (spyx total)
    
    ;=> values => (0 1 2 3 4 5)
    ;=> total => 15
    

    但是,“减少功能”可以做任何事情。它还可以返回一个序列,而不仅仅是一个标量值:

    (def duplicate (reduce (fn [cum-result new-val]         ; accumulating function
                            (conj cum-result new-val))
                          []                                ; initial value
                          values))                          ; sequence to process
    (spyx duplicate)
    ;=> duplicate => [0 1 2 3 4 5]
    

    这是一个计算输入序列积分的更复杂的归约函数:

    (def integral (reduce (fn [cum-state new-val]             ; accumulating function
                            (let [integ-val (+ (:running-total cum-state)  new-val) ]
                              { :integ-vals     (conj (:integ-vals cum-state) integ-val)
                                :running-total  integ-val} ))
                          {:integ-vals [] :running-total 0}   ; initial value
                          values))                            ; sequence to process
    (spyx integral)
    ;=> integral => {:integ-vals [0 1 3 6 10 15], :running-total 15}
    

    所以这是与map 相比的最大区别。我们称map为:

    (def y (map f x))
    

    其中xy 是序列,结果看起来像

    y(0) = f( x(0) )   ; math notation used here
    y(1) = f( x(1) )
    y(2) = f( x(2) )
    ...
    

    所以每个 y(i)依赖于函数 f 和 x(i)。相比之下,我们将reduce 定义为:

    (def y (reduce f init x))

    其中xy 是序列,init 是标量(如0[])。结果看起来像

    y(0) = f( init, x(0) )   ; math notation used here
    y(1) = f( y(0), x(1) )
    y(2) = f( y(1), x(2) )
    ...
    

    所以归约函数f2个值的函数:累加结果和新的x值。

    【讨论】:

    • “转换器可以被认为是一个优化的reducer实现”...嗯?转换器应该将一个减速器“转换”为另一个减速器。它是从减速器空间到减速器空间的映射。它本身不是减速器。
    • “传感器直接组合,不知道输入或中间聚合的创建。”不创建中间序列是这里的好处,因此与一次应用多个序列操作(map、filter、reduce...)相比,内存流失更少。
    【解决方案2】:

    当您从博客中查看reducer 函数的来源时:

    (defn reducer
      ([coll xf]
       (reify
        clojure.core.protocols/CollReduce
        (coll-reduce [_ f1 init]
          (clojure.core.protocols/coll-reduce coll (xf f1) init)))))
    

    您可以看到,reducer 要求集合对自身进行归约(通过在 coll 上调用 coll-reduce)并提供通过调用归约函数转换器 ((xf f1)) 生成的归约函数。

    所以我会说那句话:

    reducer 是 reducible collectionreducible function transformer(后来称为transducer)的组合,而不是 reduce function。

    更准确,因为您首先需要的是可约集合约简函数转换器。归约函数只是调用归约函数转换器的结果。

    【讨论】:

      猜你喜欢
      • 2012-09-26
      • 1970-01-01
      • 2021-11-06
      • 1970-01-01
      • 2015-04-25
      • 2017-07-29
      • 1970-01-01
      • 2013-05-07
      • 1970-01-01
      相关资源
      最近更新 更多