如何解决为什么 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 函数实现是错误的。 结果,我拉入了 ramda.js 并使用了 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]
使用,每次沿着输入前进一步。
因此,传感器是一种融合/森林砍伐技术。直观地说,嵌套折叠应该融合,消除了通过内部折叠创建临时结构的需要,这样它就可以被包装折叠消耗掉;遵循换能器规则让我们能够做到这一点。
那么,reduce
和 mapReducer
的名称是错误的。它们实际上是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 举报,一经查实,本站将立刻删除。