函数式思维(二)-- 为何你想不到用 reduce

上次我写了一篇简单介绍函数式思维的文章,我们组的同学看了之后表示很感兴趣,希望我有空多写写这方面的内容,然后表示他能想到用数组的 map,但是想不到 reduce。我想这可能也是个普遍现象,因为在对 FP(函数式编程)接触不多的同学来讲,脑海中对 map 的印象,可能基本等同于循环,而对 reduce 就相对陌生。但其实呢,reduce 是个比 map、flatMap 啥的更通用的函数,你可以用 reduce 轻易地实现其他函数。

我们先实现一下 reduce:

// foldl
const reduce = (reducer, acc) => list => {
    const [head, ...rest] = list;
    if (head === undefined) return acc;

    return reduce(reducer, reducer(acc, head))(rest);
};

JS 里 Array.prototype.reduce 跟我这个稍有不同,它的 reducer 可以接收四个参数(比我的版本多了 currentIndex 和 array),有 currentIndex 这个参数,就告诉我们它的实现大概率是通过循环做的,说实话个人感觉后面两个参数基本是没用的,其他语言里的实现一般也没这两个参数。

然后再解释下为啥我的 reduce 不是直接接收三个参数,而要用部分应用的形式,先接收两个,返回一个接收一个参数的函数呢?是为了复用,我们看个例子,用 reduce 实现一个 sum 函数,把数组里的元素都累加起来:

const arr = [1, 2, 3]; // 下文所有的 arr 都是这个

const sum = reduce((acc, x) => acc + x, 0);
sum(arr); // => 6

reduce 在接收不同的 reducer 和 acc 的时候会返回不同的函数,这里是返回 sum,是不是就很顺。而如果 reduce 是一个接收三个参数的函数,那 sum 就得是const sum = (arr) => reduce((acc, x) => acc + x, 0, arr),也不是不可以,但比较丑陋。

接下来我们用 reduce 实现数组的其他方法:length、map、flatMap、includes、find

// JS 的 Array.length 跟我这个实现不一样,
// arr[100] = 1,arr.length 就为 101 了,因为 JS 的 Array 本质是对象
const length = reduce(acc => acc + 1, 0);
length(arr); // => 3

const map = func => reduce((acc, x) => [...acc, func(x)], []);
map(x => x + 1)(arr); // => [2, 3, 4]

const flatMap = func => reduce((acc, x) => [...acc, ...func(x)], []);
flatMap(x => [x + 1])(arr); // => [2, 3, 4]

const includes = element => reduce((acc, x) => acc || (x === element), false);
includes(1)(arr); // => true

// 找到第一个符合条件的元素返回,否则返回 undefined
const find = func => reduce((acc, x) => acc || (func(x) ? x : undefined), undefined);
find(x => x > 2)(arr); // => 3

我们有时候处理字符串,也会想用到 reduce、map 啥的,那我们就给 String.prototype 加上:

// string
String.prototype.reduce = function (reducer, acc) {
    return reduce(reducer, acc)(this.split(''));
};

String.prototype.map = function (func) {
    return map(func)(this.split('')).join('');
};

const str = '123';
str.reduce((acc, x) => acc + Number(x), 0); // => 6
str.map(x => Number(x) + 1); // => '234'

emmmmmmm……就这样吧。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏JMCui

MongoDB系列六(聚合).

 一、概念     使用聚合框架可以对集合中的文档进行变换和组合。基本上,可以用多个构件创建一个管道(pipeline),用于对一连串的文档进行处理。这些构件包...

87860
来自专栏微信公众号:Java团长

你一直弄不懂的Java反射机制

Java反射机制, 啧啧, 当你看到这几个字的时候就有一种不好的预感, 没错, 这个东西是不怎么好理解, 所以特开此篇, 从实用的角度, 用确切的代码来讲解一下...

16410
来自专栏JackieZheng

并发和多线程-八面玲珑的synchronized

上篇《并发和多线程-说说面试常考平时少用的volatile》主要介绍的是volatile的可见性、原子性等特性,同时也通过一些实例简单与synchronized...

11930
来自专栏从流域到海域

《Java程序设计基础》 第3章手记

《Java程序设计基础》 第3章手记 本章主要内容: 1. 数据类型 2. 变量 3. 基本类型变量 4. 数据类型的转换规则 ...

20960
来自专栏飞雪无情的博客

从Java到Golang快速入门

Golang从09年发布,中间经历了多个版本的演进,已经渐渐趋于成熟,并且出现了很多优秀的开源项目,比如我们熟知的docker,etcd,kubernetes等...

13330
来自专栏Jerry的SAP技术分享

使用JavaScript ES6的新特性计算Fibonacci(非波拉契数列)

Java程序员面试系列-什么是Java Marker Interface(标记接口)

11530
来自专栏代码世界

Python之面向对象四

面向对象进阶 一、关于面向对象的两个内置函数 isinstance   判断类与对象的关系    isinstance(obj,cls)检查obj是否是类 cl...

396130
来自专栏Ryan Miao

java基础面试题

参考:http://blog.csdn.net/jackfrued/article/details/44921941 说未经允许不转载,我只好参考了。 1.面向...

37550
来自专栏佳爷的后花媛

java学习要点

作为一个程序员,在找工作的过程中,都会遇到笔试,而很多笔试里面都包括java,尤其是作为一个Android开发工程师,java是必备技能之一.所以为了笔试过程中...

37250
来自专栏Coco的专栏

高性能Javascript--高效的数据访问

10820

扫码关注云+社区

领取腾讯云代金券