让我们从一个定义开始:transducer是一个接受reducer函数并返回reducer函数的函数。
reducer是一个二进制函数,它接受一个累加器和一个值并返回一个累加器。一个reducer可以用reduce函数执行(注意:所有函数都经过 curry 处理,但是为了便于阅读,我已经介绍了这以及pipecompose的定义-您可以在live demo中看到它们):

const reduce = (reducer, init, data) => {
  let result = init;
  for (const item of data) {
    result = reducer(result, item);
  }
  return result;
}

使用reduce,我们可以实现mapfilter函数:

const mapReducer = xf => (acc, item) => [...acc, xf(item)];
const map = (xf, arr) => reduce(mapReducer(xf), [], arr);

const filterReducer = predicate => (acc, item) => predicate(item) ?
  [...acc, item] :
  acc;
const filter = (predicate, arr) => reduce(filterReducer(predicate), [], arr);

如我们所见,mapfilter之间有一些相似之处,并且这两个函数仅适用于数组。另一个缺点是,当我们组合这两个函数时,在每个步骤中都会创建一个临时数组,该数组将传递给另一个函数。

const even = n => n % 2 === 0;
const double = n => n * 2;

const doubleEven = pipe(filter(even), map(double));

doubleEven([1,2,3,4,5]);
// first we get [2, 4] from filter
// then final result: [4, 8]

换能器可以帮助我们解决这些问题:当我们使用换能器时,不会创建临时数组,并且我们可以泛化我们的函数,使其不仅适用于数组。换能器需要transduce函数才能工作换能器通常通过传递给transduce函数来执行:

const transduce = (xform, iterator, init, data) =>
  reduce(xform(iterator), init, data);

const mapping = (xf, reducer) => (acc, item) => reducer(acc, xf(item));

const filtering = (predicate, reducer) => (acc, item) => predicate(item) ?
  reducer(acc, item) :
  acc;

const arrReducer = (acc, item) => [...acc, item];

const transformer = compose(filtering(even), mapping(double));

const performantDoubleEven = transduce(transformer, arrReducer, [])

performantDoubleEven([1, 2, 3, 4, 5]); // -> [4, 8] with no temporary arrays created

我们甚至可以使用map定义数组filtertransducer,因为它很容易组合:

const map = (xf, data) => transduce(mapping(xf), arrReducer, [], data);

const filter = (predicate, data) => transduce(filtering(predicate), arrReducer, [], data);

实时版本,如果您想运行代码-> https://runkit.com/marzelin/transducers

我的推理有意义吗?

最佳答案

您的理解是正确的,但不完整。

除了您描述的概念之外,换能器还可以执行以下操作:

  • 支持早期退出语义
  • 支持完成语义
  • 是有状态的
  • 支持step函数的init值。

  • 因此,例如,使用JavaScript的实现将需要执行以下操作:
    // Ensure reduce preserves early termination
    let called = 0;
    let updatesCalled = map(a => { called += 1; return a; });
    let hasTwo = reduce(compose(take(2), updatesCalled)(append), [1,2,3]).toString();
    console.assert(hasTwo === '1,2', hasTwo);
    console.assert(called === 2, called);
    

    在这里,由于调用了take,还原操作将尽早解救。

    它需要能够(可选)调用不带任何初始值参数的step函数:
    // handles lack of initial value
    let mapDouble = map(n => n * 2);
    console.assert(reduce(mapDouble(sum), [1,2]) === 6);
    

    在这里,不带参数的sum调用返回加性标识(零)以播种减少量。

    为了做到这一点,这里有一个辅助函数:
    const addArities = (defaultValue, reducer) => (...args) => {
      switch (args.length) {
        case 0: return typeof defaultValue === 'function' ? defaultValue() : defaultValue;
        case 1: return args[0];
        default: return reducer(...args);
      }
    };
    

    这需要一个初始值(或可以提供一个值的函数)和一个reduce以作为种子:
    const sum = addArities(0, (a, b) => a + b);
    

    现在sum具有适当的语义,这也是第一个示例中的append的定义方式。对于有状态转换器,请查看take(包括辅助函数):
    // Denotes early completion
    class _Wrapped {
      constructor (val) { this[DONE] = val }
    };
    
    const isReduced = a => a instanceof _Wrapped;
    // ensures reduced for bubbling
    const reduced = a => a instanceof _Wrapped ? a : new _Wrapped(a);
    const unWrap = a => isReduced(a) ? a[DONE] : a;
    
    const enforceArgumentContract = f => (xform, reducer, accum, input, state) => {
      // initialization
      if (!exists(input)) return reducer();
      // Early termination, bubble
      if (isReduced(accum)) return accum;
      return f(xform, reducer, accum, input, state);
    };
    
    /*
     * factory
     *
     * Helper for creating transducers.
     *
     * Takes a step process, intial state and returns a function that takes a
     * transforming function which returns a transducer takes a reducing function,
     * optional collection, optional initial value. If collection is not passed
     * returns a modified reducing function, otherwise reduces the collection.
     */
    const factory = (process, initState) => xform => (reducer, coll, initValue) => {
      let state = {};
      state.value = typeof initState === 'function' ? initState() : initState;
      let step = enforceArgumentContract(process);
      let trans = (accum, input) => step(xform, reducer, accum, input, state);
      if (coll === undefined) {
        return trans; // return transducer
      } else if (typeof coll[Symbol.iterator] === 'function') {
        return unWrap(reduce(...[trans, coll, initValue].filter(exists)));
      } else {
        throw NON_ITER;
      }
    };
    
    const take = factory((n, reducer, accum, input, state) => {
      if (state.value >= n) {
        return reduced(accum);
      } else {
        state.value += 1;
      }
      return reducer(accum, input);
    }, () => 0);
    

    如果您想看到所有这些信息,我前一阵子做了little library。尽管我忽略了Cognitect的互操作协议(protocol)(我只是想获得概念),但我还是根据来自Strange Loop和Conj的Rich Hickey的讲话尝试了尽可能准确地实现语义。

    10-04 14:00