3

示例代码:

// Compose functionality
const compose = (...fns) => {
  return args => {
    return fns.reduceRight((arg, fn) => fn(arg), args);
  }
};

// List of transformation and predicate functions
const add1 = x => x + 1;
const isGreaterThanThree = x => x > 3;
const times2 = x => x * 2;

// Concat and Sum reducers (or the thing that I want to build). 
/*
  In this case, I'm using concatReducer, but I can easily substitute concatReducer with
  sumReducer and change the initial value of the reduce method to zero.
*/
const concatReducer = (acc, el) => acc.concat(el);
const sumReducer = (acc, el) => acc += el;

// Transformation reducer (not sure the appropriate terminology)
const mapReducer = transform => {
  return reducer => {
    return (acc, el) => {
      return reducer(acc, transform(el));
    }
  }
};

// Predicate reducer (again, not sure the appropriate terminology here)
const filterReducer = predicate => {
  return reducer => {
    return (acc, el) => {
      return predicate(el) ? reducer(acc, el) : acc;
    }
  }
}

[1, 2, 3] 
  .reduce(
    compose(
      mapReducer(times2),
      filterReducer(isGreaterThanThree),
      mapReducer(add1),
    )(concatReducer),
    []
  );

我希望该值为 [ 8 ] 而不是 [ 5, 7 ]。

Compose 是右关联的 (reduceRight),但在这种情况下,它表现为左关联。

我心想,也许我的 compose 函数实现是错误的。结果,我拉入了并使用了 R.compose,但得到了相同的结果。

难道我做错了什么?或者这是在处理传感器时组合是左关联的场景之一?

4

2 回答 2

2

引用和一些示例取自https://github.com/cognitect-labs/transducers-js

什么是换能器?

换能器只是一个元的函数。唯一的参数是另一个转换器转换器(在代码库中标记为 xf)。

由于传感器只是一个参数的函数,因此可以通过函数组合轻松组合它们以创建转换器管道。请注意,转换器在调用时会返回转换器。

示例:(改编)

var mapper = function(f) {
  return function(xf) {    // <- This is a transducer, it takes a transformer xf
    return Mapper(f, xf);  // <- and it returns another transformer xf'
  };
};

注意:我们稍后会介绍Mapper

让我们用一些箭头函数重写:

var mapper = f => xf => Mapper(f, xf);
//                ^^^^^^^^^^^^^^^^^^^
//                This is a transducer

在上面的代码片段中,应该更清楚的是,transducer 确实是一个接受一个转换器并返回另一个转换器的函数。这两个组成是相等的:

compose(mapper(double), mapper(inc))

compose(xf => Mapper(double, xf), xf => Mapper(inc, xf))

有趣的事实

函数组合现在等待初始转换器,该转换器通常称为“步进”转换器(或“步进”函数),负责将转换累积到容器中。

典型的容器可以是数组、字符串或对象。这种“步进”变压器将:

  • 推送到一个数组(我们将在这个答案的其余部分中提到这样的转换器)Push
  • 或附加到字符串
  • 或在对象上设置属性

但是我们不需要知道这种变压器的细节。但是,我们确实需要了解什么是变压器。

什么是变压器?

变形金刚是对象。他们必须实现 3 个方法@@transducer/init@@transducer/result@@transducer/step。如果打算将变压器与其他变压器组合在一起,则它们应关闭下一个变压器或将其存储在现场中。

示例:(改编)

var Mapper = function(f, xf) {
  return { // <-- This is a transformer
    "@@transducer/init": function() { 
      return xf["@@transducer/init"](); 
    },
    "@@transducer/result": function(result) { 
      return xf["@@transducer/result"](result); 
    },
    "@@transducer/step": function(result, input) {
      return xf["@@transducer/step"](result, f(input));
      //                                     ^^^^^^^^
      //                                     e.g. inc(41)
    }
  };
};

为了理解为什么transducers会反转组合的顺序,我们需要仔细看看这个@@transducer/step方法:

"@@transducer/step": function(result, input) {
  return xf["@@transducer/step"](result, f(input));
//       ^^^^^^^^^^^^^^^^^^^^^^^         ^
//       called last                     called first!

注意:result是我们将在其中累积转换的容器。

当你这样做时:

compose(mapper(double), mapper(inc))(Push())

你最终得到一个看起来像这样的最终转换器:

const xfinal = Mapper(double, Mapper(inc, push));

通常您的库会为您执行此操作,但出于教育目的,我们将调用@@transducer/step最终转换器上的方法并分解函数调用:

xfinal['@@transducer/step']([], 20)

类似于:

Mapper(double, Mapper(inc, push))([], 20)
       ^^^^^^  ^^^^^^^^^^^^^^^^^
       f       xf

Mapper(inc, push)([], double(20))
^^^^^^^^^^^^^^^^^
xf     ^^^  ^^^^
       f'   xf'

push([], inc(double(20)))
^^^^     ^^^ ^^^^^^
xf'      f'  f
         ^^^^^^^^^^^^^^^
         HERE!

即使我们这样做了compose(mapper(double), mapper(inc)),我们也可以看到该double函数之前 inc已应用。这不是compose函数中的错误,它只是变压器组合在一起时应该如何工作。

于 2021-04-02T10:24:34.040 回答
1

因为您将组合的传感器 ( f,g,h) 应用于减速器 ( r),所以最左边的 ( f) 成为最顶部的,首先作用于(acc, el)增强的减速器的最终参数f( g(h(r)) )(*)

compose(f,g,h)(r)(acc, el) =~= f( g( h( r)) )(acc, el)
                                  <--------
                               --...........->

事实上,最右边的 ,首先h应用于论点;但这也是一个功能;整个组成的东西是一个函数;并且组合函数具有作为其第一个组件,因此它首先根据 的定义使用内部组合的结果来处理下一个参数。r rff(acc, el) g( h( r))f

我们在这里拥有的是

       r        //  reducer
    h           // transducer
    h( r )      //  reducer
  g             // transducer
  g(h( r ))     //  reducer
f               // transducer
f(g(h( r )))    //  reducer

(*)所以你已经定义了你的函数,所以

mapReducer(foo)(reducer)(acc, el) 
  =~= reducer(acc, foo(el));

filterReducer(predicate)(reducer)(acc, el)
  =~= predicate(el) ? reducer(acc, el) : acc;

concatReducer(acc, el)
  =~= acc.concat(el);

而且,最重要的是,

compose(f,g,h,...,p,q)(r)
    =~= [f,g,h,...,p,q].reduceRight((acc, fn) => fn(acc), r);
    =~= f(g(h(...(p(q( r ))...)))

那么那么

[1, 2, 3] 
  .reduce(
    compose(
      f,               // f = mapReducer(times2),
      g,               // g = filterReducer(isGreaterThanThree),
      h                // h = mapReducer(add1),
      )(r),            // r = concatReducer
    []
  )
  =~= [1, 2, 3] .reduce( f(g(h(r))), [])     // rc = f(g(h(r)))
  =~= rc( rc( rc( [], 1),  2),  3)
  =~= rc( rc( f(g(h(r)))( [], 1),  2),  3)
  =~= rc( rc( mapReducer(times2)(g(h(r)))( [], 1 ),  2),  3)

           // mapReducer(foo   )(reducer)(acc, el)
           //                =~= reducer( acc, foo(el))

  =~= rc( rc( g(h(r))([], times2( 1)),  2),  3)
  =~= rc( rc( filterReducer(isGreaterThanThree)(h(r))([] , times2( 1)),  
            2), 
        3)

           // filterReducer(pred           )(reducer)(acc, el)
           //                =~= pred(el) ?  reducer( acc, el) : acc

  =~= rc( rc( isGreaterThanThree( twice1) ? h(r)(  [], twice1) : [],  
            2),          /* where twice1 = times2(1) 
                                  h = mapReducer( add1) */
        3)
  =~= rc( rc( isGreaterThanThree( twice1) ? r([], add1(twice1)) : [],  
            2),          /* where twice1 = times2(1) 
                                  r = concatReducer  */
        3)
  =~= rc( rc( isGreaterThanThree( twice1) ? [].concat(add1(twice1)) : [],  
            2),          /* where twice1 = times2(1) */
        3)
  =~= ...

我们可以看到它首先mapReducer(times2)在列表的元素上起作用,然后是过滤,然后是映射。isGreaterThanThreeadd1

在处理 时[1,2,3],首先完成 的映射times2(隐含地好像在制作它[2,4,6]),然后是过滤(将只留下 [4,6]),然后是 的映射,以获得确实add1的最终结果。[5,7]

虽然没有创建临时结构。而是构建了一个复合减速器,并由 one 使用,一次reduce沿着输入一步前进。

因此,传感器是一种融合/森林砍伐技术。直观地说,嵌套折叠应该融合,消除了仅通过内部折叠创建临时结构的需要,以便它可以被包裹折叠消耗;遵循传感器规则可以让我们实现这一目标。


于是,mapReducer名不副实filterReducer。它们实际上是mappingTransducer_Maker(或只是mapping)和filteringTransducer_Maker(或只是filtering),其中

  • mapping(foo)是一个映射转换器,适用foo于输入的元素elt,导致foo(elt)
  • filtering(pred)是一个过滤el转换器,它根据谓词调用过滤掉输入的元素pred(el),或者保留它。

或者更确切地说,transducer 增加了它的参数reducer,以便当最终使用累加器和当前元素调用组合的、增强的 reducer 时,它会在将结果传递给 base 之前以规定的方式操作元素和累加器reducer

mapping(foo)( filtering(pred)(reducer) )( acc, elt)
----------------------------------------(         )
 =~=
filtering(pred)(reducer)( acc, foo(elt) )
 =~=
pred( foo(elt) ) ? reducer( acc, foo(elt) ) : acc

因此,mapping(foo)filtering(pred)就他们的论点而言是从右到左组成的,reducer; 但随后组合的 reducer 从顶部开始工作,从左到右跟踪传感器的效果——首先进行映射,然后进行过滤。

于 2021-04-02T11:56:03.777 回答