Javascript 如何以正确的顺序链接映射和筛选函数

Javascript 如何以正确的顺序链接映射和筛选函数,javascript,functional-programming,Javascript,Functional Programming,我非常喜欢链接Array.prototype.map、过滤器和reduce来定义数据转换。不幸的是,在最近的一个涉及大型日志文件的项目中,我再也不能通过多次循环我的数据来逃脱 我的目标是: 我想创建一个函数来链接.filter和.map方法,而不是立即在数组上映射,组成一个函数,在数据上循环一次。即: 我的尝试: 受此启发,我开始编写一个Transduce函数 const filterer = pred => reducer => (acc, x) => pred(x)

我非常喜欢链接
Array.prototype.map
过滤器
reduce
来定义数据转换。不幸的是,在最近的一个涉及大型日志文件的项目中,我再也不能通过多次循环我的数据来逃脱

我的目标是: 我想创建一个函数来链接
.filter
.map
方法,而不是立即在数组上映射,组成一个函数,在数据上循环一次。即:

我的尝试: 受此启发,我开始编写一个
Transduce
函数

const filterer = pred => reducer => (acc, x) =>
    pred(x) ? reducer(acc, x) : acc;

const mapper = map => reducer => (acc, x) =>
    reducer(acc, map(x));

const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({
    map: map => Transduce(mapper(map)(reducer)),
    filter: pred => Transduce(filterer(pred)(reducer)),
    run: arr => arr.reduce(reducer, [])
});
问题是: 上面的
Transduce
代码段的问题是它“向后”运行。。。最后一个方法I链是第一个要执行的方法:

const someTransformation = Transduce()
    .map(x => x + 1)
    .filter(x => x > 3)
    .map(x => x / 2);

// Instead of [ 2, 2.5 ] this returns []
//  starts with (x / 2)       -> [0.5, 1, 1.5, 2] 
//  then filters (x < 3)      -> [] 
const myData = someTransformation.run([ 1, 2, 3, 4]);
致:

Transducer(concat).map(f).map(g) == (acc, x) => concat(acc, g(f(x)))
这类似于:

mapper(f) (mapper(g) (concat))
我想我理解它为什么会发生,但我不知道如何在不改变函数“接口”的情况下修复它

const filterer = pred => reducer => (acc, x) =>
    pred(x) ? reducer(acc, x) : acc;

const mapper = map => reducer => (acc, x) =>
    reducer(acc, map(x));

const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({
    map: map => Transduce(mapper(map)(reducer)),
    filter: pred => Transduce(filterer(pred)(reducer)),
    run: arr => arr.reduce(reducer, [])
});
问题是: 我如何才能使我的
转换
方法链
过滤器
映射
操作按正确顺序进行


笔记:
  • 我只是在学习一些我想做的事情的命名。请告知我是否错误使用了
    变速器
    术语,或者是否有更好的方法来描述问题
  • 我知道我可以使用嵌套的
    for
    循环执行相同的操作:
const push=(acc,x)=>(acc.push(x,acc);
const ActionChain=(actions=[])=>{
const run=arr=>
arr.reduce((acc,x)=>{
for(设i=0,动作;ifn=>
ActionChain(push(actions,{type,fn}));
返回{
映射:addAction(“映射”),
过滤器:添加操作(“过滤器”),
跑
};
};
//与常规链条进行比较,检查是否
//性能有所提高
//诚然,在这个例子中,它非常小。。。
常数法={
运行:arr=>
啊
.map(x=>x+3)
.filter(x=>x%3==0)
.map(x=>x/3)
.filter(x=>x<40)
};
const actionChain=actionChain()
.map(x=>x+3)
.filter(x=>x%3==0)
.map(x=>x/3)
.filter(x=>x<40)
const testData=Array.from(数组(100000),(x,i)=>i);
时间(“天真”);
const result1=naiveApproach.run(testData);
console.timeEnd(“天真”);
控制台。时间(“链”);
constresult2=actionChain.run(testData);
控制台。时间结束(“链”);

log(“equal:,JSON.stringify(result1)==JSON.stringify(result2))我认为您需要更改实现的顺序:

const filterer = pred => reducer => (x) =>pred((a=reducer(x) )?x: undefined;

const mapper = map => reducer => (x) => map(reducer(x));
然后,您需要将run命令更改为:

run: arr => arr.reduce((a,b)=>a.concat([reducer(b)]), []);
默认的减速机必须是

x=>x
但是,这种方式过滤器无法工作。您可以在filter函数中抛出undefined,在run函数中抛出catch:

run: arr => arr.reduce((a,b)=>{
try{
 a.push(reducer(b));
}catch(e){}
return a;
}, []);

const filterer = pred => reducer => (x) =>{
 if(!pred((a=reducer(x))){
   throw undefined;
 }
 return x;
};

然而,总的来说,我认为for循环在这种情况下要优雅得多…

在我们了解更多之前

我真的很喜欢锁链

我明白了,我会安抚你,但你会明白,强迫你的程序通过链接API是不自然的,而且在大多数情况下比它的价值更麻烦

我想我理解它为什么会发生,但我不知道如何在不改变函数“接口”的情况下修复它

const filterer = pred => reducer => (acc, x) =>
    pred(x) ? reducer(acc, x) : acc;

const mapper = map => reducer => (acc, x) =>
    reducer(acc, map(x));

const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({
    map: map => Transduce(mapper(map)(reducer)),
    filter: pred => Transduce(filterer(pred)(reducer)),
    run: arr => arr.reduce(reducer, [])
});
问题确实出在
Transduce
构造函数上。您的
map
filter
方法将
map
pred
堆叠在传感器链的外部,而不是嵌套在内部

下面,我已经实现了您的
Transduce
API,它以正确的顺序评估映射和过滤器。我还添加了一个
log
方法,以便查看
Transduce
的行为

const-Transduce=(f=k=>k)=>({
地图:g=>
转换(k=>
f((acc,x)=>k(acc,g(x)),
过滤器:g=>
转换(k=>
f((acc,x)=>g(x)?k(acc,x):acc)),
日志:s=>
转换(k=>
f((acc,x)=>(console.log(s,x),k(acc,x)),
运行:xs=>
x.reduce(f((acc,x)=>acc.concat(x)),[])
})
常量foo=nums=>{
返回转换器()
.log('大于2?')
.filter(x=>x>2)
.log('\tsquare:')
.map(x=>x*x)
.log(“\t\t小于30”)
.filter(x=>x<30)
.log('\t\t\tpass')
.run(nums)
}
//对于所有n个NUM,保持正方形(n)
//其中n>2
//其中平方(n)<30
日志(foo([1,2,3,4,5,6,7]))

//=>[9,16,25]
map:map=>Transduce(映射器(映射器)(还原器)),意思是:返回一个先映射的函数,然后执行原始还原。转换器构建堆栈。尝试
run:arr=>arr.reduceRight(reducer,[])
@ft或者我想让
run
中的
reducer
成为之前传递的所有
map
filter
方法的组合。我认为问题不在于我用数据减少我的
arr
的顺序,而在于我无法正确返回新的
reducer
方法。例如:
mapper(f)(mapper(g)(concat))
返回一个类似于concat(acc,x)=>concat(acc,g(f(x))
,而my
Transduce(concat).map(f).map(g)
返回
(acc,x)=>concat(acc,f(x))
我认为这不起作用,因为my
pred
x->bool
的函数,
map
x->y
。这使得reducer首先运行,返回一个数组(至少在我当前的示例中是这样)。还是我遗漏了什么?@user3297291你是对的。您还需要更改run和默认的reducer。问题的关键在于支持
过滤器。我同意
const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({
  map: map => Transduce(mapper(map)(reducer)),
  filter: pred => Transduce(filterer(pred)(reducer)),
  run: arr => arr.reduce(reducer, [])
});