Javascript中transducer的应用

本文假定你对下列知识有一定了解

  • 函数式编程
  • 高阶函数
  • 柯里化
  • ES6语法

需求背景

假定有一数组,

const testArray = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

要筛选出所有大于3的元素,然后再加1,组成新的数组[5, 6, 7, 8, 9, 10].
用命令式编程很容易实现:

// 算法1
let result = [];
testArray.forEach(x => {
    if (greaterThanThree(x)) {
        result.push(increaseOne(x));
    }
});
console.log(result);

用函数式编程,最简单的方式是这样:

// 算法2
const result = testArray.filter(greaterThanThree).map(increaseOne);
console.log(result);

看似代码少了很多,但是效率却下降了。算法1的时间复杂度是O(n), 算法2的时间复杂度是O(2*n).
对于这种情况,如何用函数式编程实现O(n)的算法?
首先,filter和map都可以用reduce来实现,算法2可以转化为如下代码:

// 算法3
const greaterThanThree = x => x > 3;
const filterReducer = (acc, element) => {
    return greaterThanThree(element) ? acc.concat(element) : acc;
};

const increaseOne = x => x + 1;
const mapReducer = (acc, element) => {
    return acc.concat(increaseOne(element));
};
let result = restArray.reduce(filterReducer, []).reduce(mapReducer, []);
console.log(result);

于是,思路是将filterReducer和mapReducer组装成一个Reducer,作为参数传给restArray.reduce。

第一步

将函数greaterThanThree和increaseOne作为参数提出来,于是filterReducer和mapReducer转化为:

const filterReducer = (acc, element, predicate) => {
    return predicate(element) ? acc.concat(element) : acc;
};

const mapReducer = (acc, element, transform) => {
    return acc.concat(transform(element));
};

但是,因为需要先组装filterReducer和mapReducer,再传给reduce,所以参数是分开传入的。并且acc和element是在执行reduce时最后传入,所以需要柯里化:

const filterReducer = predicate => (acc, element) => {
    return predicate(element) ? acc.concat(element) : acc;
};

const mapReducer = transform => (acc, element) => {
    return acc.concat(transform(element));
};

第二步

进一步抽象。filterReducer和mapReducer都有concat。如果要把他们组装成一个Reducer,必须只有一个concat,所以concat也要当参数传入。目前是调用数组concat方法,为了能参数化,必须重写:

const concat = (acc, element) => acc.concat(element);

然后,就可以参数化concat:

const filterReducer = predicate => concatReducer => (acc, element) => {
    return predicate(element) ? concatReducer(acc, element) : acc;
};

const mapReducer = transform => concatReducer => (acc, element) => {
    return concatReducer(acc, transform(element));
};

至此,filterReducer和mapReducer就转化为了transducer。

第三步

引入compose:

var compose = (f, g) => x => {
    return f(g(x));
};

由于需求是先筛选后转化(加1),所以思路是将mapReducer作为concatReducer传入filterReducer:

const newReducer = compose(filterReducer(greaterThanThree), mapReducer(increaseOne)); 

上文说过,必须只有一个concat,所以concat在组装后再传入:

const result  = testArray.reduce(newReducer(concat), []);

完整代码

// 算法4
var compose = (f, g) => x => f(g(x));

const greaterThanThree = x => x > 3;
const increaseOne = x => x + 1;
const concat = (acc, element) => acc.concat(element);
const filterReducer = predicate => concatReducer => (acc, element) => {
    return predicate(element) ? concatReducer(acc, element) : acc;
};

const mapReducer = transform => concatReducer => (acc, element) => {
    return concatReducer(acc, transform(element));
};

const newReducer = compose(filterReducer(greaterThanThree), mapReducer(increaseOne)); 
const result  = testArray.reduce(newReducer(concat), []);
console.log(result);

如何理解newReducer(concat)

newReducer(concat)等价于

compose(filterReducer(greaterThanThree), mapReducer(increaseOne))(concat)

代入compose等价于

filterReducer(greaterThanThree)(mapReducer(increaseOne)(concat))

代入filterReducer等价于

(acc, element) => {
    return greaterThanThree(element) ? mapReducer(increaseOne)(concat)(acc, element) : acc;
};

代入mapReducer等价于

(acc, element) => {
    return greaterThanThree(element) ? ((acc, element) => {
        return concat(acc, increaseOne(element));
    })(acc, element) : acc;
};

等价于

(acc, element) => {
    return greaterThanThree(element) ? concat(acc, increaseOne(element)) : acc;
};

到这里,也许你会恍然大悟。如果不考虑各种扩展重用,只是要快点解决这个性能问题,但又要守住函数式编程的底线,你只需要直接用这个reducer即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值