微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

为什么 compose 使用换能器从左到右应用?

如何解决为什么 compose 使用换能器从左到右应用?

示例代码

// 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,但我得到了相同的结果。

我做错了吗?或者这是组合在处理换能器时左关联的场景之一?

解决方法

引述和一些例子来自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

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

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)
    }
  };
};

为了理解为什么换能器会颠倒组合顺序,我们需要仔细研究一下 @@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,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 函数中的错误,而是转换器组合在一起时应该如何工作。

,

因为您将组合转换器 (f,g,h) 应用到减速器 (r),所以最左边的 (f) 变为最顶部,首先对最终参数 {{1} } 到增强的减速器 (acc,el):(*)

f( g(h(r)) )

确实,最右边的 compose(f,h)(r)(acc,el) =~= f( g( h( r)) )(acc,el) <-------- --...........-> 应用于参数 h first;但是 r 也是一个函数;整个组合物是一个函数;并且组合函数将 r 作为其第一个组件,因此 f 开始处理 next 参数 f 首先,根据(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,h,...,p,q)(r)
    =~= [f,q].reduceRight((acc,fn) => fn(acc),r);
    =~= f(g(h(...(p(q( r ))...)))

我们可以看到 [1,2,3] .reduce( compose( f,// f = mapReducer(times2),// g = filterReducer(isGreaterThanThree),h // h = mapReducer(add1),)(r),// r = concatReducer [] ) =~= [1,3] .reduce( f(g(h(r))),[]) // rc = f(g(h(r))) =~= rc( rc( rc( [],1),2),3) =~= rc( rc( f(g(h(r)))( [],3) =~= rc( rc( mapReducer(times2)(g(h(r)))( [],1 ),3) // mapReducer(foo )(reducer)(acc,el) // =~= reducer( acc,foo(el)) =~= rc( rc( g(h(r))([],times2( 1)),3) =~= rc( rc( filterReducer(isGreaterThanThree)(h(r))([],3) // filterReducer(pred )(reducer)(acc,el) // =~= pred(el) ? reducer( acc,el) : acc =~= rc( rc( isGreaterThanThree( twice1) ? h(r)( [],twice1) : [],/* where twice1 = times2(1) h = mapReducer( add1) */ 3) =~= rc( rc( isGreaterThanThree( twice1) ? r([],add1(twice1)) : [],/* where twice1 = times2(1) r = concatReducer */ 3) =~= rc( rc( isGreaterThanThree( twice1) ? [].concat(add1(twice1)) : [],/* where twice1 = times2(1) */ 3) =~= ... 处理列表元素,然后通过 mapReducer(times2) 过滤,然后 isGreaterThanThree 的映射。

在处理 add1 时,首先完成 [1,3] 的映射(隐式 as-if 使其成为 times2),然后过滤(这将只留下 [2,4,6]),然后是 [4,6] 的映射,最终得到 add1 的结果。

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

因此,传感器是一种融合/森林砍伐技术。直观地说,嵌套折叠应该融合,消除了通过内部折叠创建临时结构的需要,这样它就可以被包装折叠消耗掉;遵循换能器规则让我们能够做到这一点。


那么,reducemapReducer 的名称是错误的。它们实际上是filterReducer(或只是mappingTransducer_Maker)和mapping(或只是filteringTransducer_Maker),其中

  • filtering 是一个 mapping 变换器,它将 mapping(foo) 应用到输入的元素 foo,产生 elt,并且
  • foo(elt) 是一个 filtering 转换器,它根据谓词调用 filtering(pred) 过滤掉输入的元素 el,或者将其保留。

或者更确切地说,转换器增加它的参数 pred(el) 以便当最终使用累加器和当前元素调用组合的、增强的 reducer 时,它在传递结果之前以规定的方式操作元素和累加器到基地reducer

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);但随后组合式减速器从顶部开始工作,从左到右跟踪换能器的效果——首先进行映射,然后进行过滤。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?