1. 程式人生 > >函式式思維(二)-- 為何你想不到用 reduce

函式式思維(二)-- 為何你想不到用 reduce

上次我寫了一篇簡單介紹函式式思維的文章,我們組的同學看了之後表示很感興趣,希望我有空多寫寫這方面的內容,然後表示他能想到用陣列的 map,但是想不到 reduce。我想這可能也是個普遍現象,因為在對 FP(函數語言程式設計)接觸不多的同學來講,腦海中對 map 的印象,可能基本等同於迴圈,而對 reduce 就相對陌生。但其實呢,reduce 是個比 map、flatMap 啥的更通用的函式,你可以用 reduce 輕易地實現其他函式。

我們先實現一下 reduce:

// foldl
const reduce = (reducer, acc) => arr => {
    // reduceRight: const x = arr.pop();
const x = arr.shift(); if (!x) return acc; return reduce(reducer, reducer(acc, x))(arr); }; 複製程式碼

JS 裡 Array.prototype.reduce 跟我這個稍有不同,它的 reducer 可以接收四個引數(比我的版本多了 currentIndex 和 array),某種程度上我的實現更 FP 一點,有 currentIndex 這個引數,基本就告訴我們它的實現是通過迴圈做的,而且這多出來的兩個引數在什麼場景下會真的必須用到呢?我想是在你 reduce 的過程中還在改變原陣列的情況下,所以需要從引數拿到更新後的 array(也就意味著產生了副作用),這本身就很不 FP,不可變性和無副作用是 FP 的重要特性,就算我們在真實開發中無法完全做到,但既然是在談 reduce,那總該儘量避免。所以個人感覺 JS 的這個 reduce 從 FP 的角度看的話有點不倫不類。

然後再解釋下為啥我的 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……就這樣吧。