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

4,168 阅读2分钟

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

我们先实现一下 reduce:

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……就这样吧。