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

  • 函数式编程
  • 高阶函数
  • 柯里化
  • 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即可。

Javascript中transducer的应用相关推荐

  1. 浅析 JavaScript 中的 函数 uncurrying 反柯里化

    柯里化 柯里化又称部分求值,其含义是给函数分步传递参数,每次传递参数后部分应用参数,并返回一个更具体的函数接受剩下的参数,这中间可嵌套多层这样的接受部分参数函数,直至返回最后结果. 因此柯里化的过程是 ...

  2. JavaScript中,this的绑定规则

    对于 JavaScript 新手来说,this 是非常基础同时也难以理解的知识点. 比如下面的代码,this 指向就有三种方式. 在<你不知道的 JavaScript>一书中,我总算比较清 ...

  3. Javascript中undefined,NaN等特殊比较

    以下内容转自: http://blog.csdn.net/hongweigg/article/details/38090093 1.问题:在Javascript中,typeof(undefined) ...

  4. Javascript中二进制数据处理方法

    Javascript中二进制数据处理方法 转载于:https://www.cnblogs.com/motadou/archive/2012/02/19/2358514.html

  5. JavaScript 中的有限状态机

    http://www.ibm.com/developerworks/cn/web/wa-finitemach/ JavaScript 中的有限状态机 Page navigation 系列文章 有限状态 ...

  6. 在Javascript中使用面向对象的编程

    by Mike Koss March 26th, 2003 这是一篇,我个人认为最好的,Javascript面向对象编程的文章.翻译不好的地方,还望大家指正,谢谢. 如果您需要,可以访问下面的地址取得 ...

  7. 取出url中的字符_如何在JavaScript中解析URL:例如主机名,路径名,查询,哈希?...

    统一资源定位符(缩写URL)是对Web资源(网页,图像,文件)的引用.URL指定资源位置和检索资源的机制(http,ftp,mailto). 例如,这是此博客文章的URL: 通常,您需要访问URL的特 ...

  8. 在javascript中判断类型

    String 一个字符串始终是一个字符串,所以这一块是很容易.除非使用new(new String)调用,否则typeof将返回"object".所以也要包含那些可以使用的字符串i ...

  9. JavaScript中几个重要的知识点(1) ---- 面向对象

    JavaScript中几个最重要的大知识点 面向对象 DOM事件 异步交互ajax 面向对象 在JS中可以把任意的引用和变量都看成是一个对象.面向对象的主要三个表现形式: 封装 继承 多态 1. 封装 ...

  10. javascript中实例方法与类方法的区别

    在javascript中,类有静态属性和实例属性之分,也有静态方法和实例方法之分 类属性(静态属性):通过类直接访问,不需要声明类的实例来访问 类方法(静态方法):通过类直接访问,不需要声明类的实例来 ...

最新文章

  1. ARP侦查工具Netdiscover
  2. python 最小二乘法三维坐标拟合平面_matlab三维曲面进行平面拟合,利用最小二乘法...
  3. 注册assembly的问题
  4. c语言getch() 头文件,用getch()需要头文件吗?
  5. html5 video在uc不自动播放,uc浏览器无法播放视频怎么办
  6. 以后的blog将转移到微信公众号,请扫码关注谢谢!
  7. 余承东:鸿蒙系统将与科大讯飞开放平台共同推动AI商业化
  8. spring ioc控制反转
  9. sqlplus 登陆报协议适配器错误
  10. [POJ2559POJ3494] Largest Rectangle in a HistogramLargest Submatrix of All 1’s 「单调栈」
  11. ABAQUS单位统一
  12. VS2022开发Arduino(提供Visual.Micro.Processing.Sketch.dll)
  13. 生活中的逻辑谬误05.特例谬误
  14. 【MODBUS通讯协议】
  15. 海信E8H Mini LED 电视评测
  16. 【作业】非结构化数据相关知识整理
  17. flutter打包安卓问题
  18. 【coolshell酷壳】你可能不知道的Shell
  19. 解决iOS 证书不受信任的问题
  20. win7我的文档里.android是什么文件夹,[转载]移动Win7用户文件夹(我的文档)默认位置至D盘...

热门文章

  1. 已知从1970年1月1日0分0秒到目前的总秒数计算当前时间
  2. 三国志战略版360区S4服务器合并信息,三国志战略版S3合区表 新赛季哪些服务器合区...
  3. 关于DJI Phantom 3 Advanced大疆精灵3A遥控器固件升级问题的解决办法
  4. Win10下用Strokeit的方法
  5. 第三方Android应用商店也有安全问题
  6. Axure实战:滑动拼图解锁教程
  7. VOT2021比赛简介
  8. 【计几】平面最短欧氏距离点对题集
  9. 从GitHub火到了CSDN,共计1658页的《Java岗面试核心MCA版》
  10. 国人自研开源项目,一款简单易用的 GitLab 替代品