純函數和柯里化很容易寫出洋葱代碼 h(g(f(x)))。洋葱代碼的嵌套問題使得我們的維護更加困難。這與我們選用函數式編程的開發初衷是相違背的,在這種情況下函數組合的概念就應運而生。
函數組合可以讓我們把細粒度的函數重新組合生成一個新的函數
下面這張圖表示程序中使用函數處理數據的過程,給 fn 函數輸入參數 a,返回結果 b。可以想想 a 數據通過一個管道得到了 b 數據。
當 fn 函數比較複雜的時候,我們可以把函數 fn 拆分成多個小函數,此時多了中間運算過程產生的 m 和n。
下面這張圖中可以想象成把 fn 這個管道拆分成了3個管道 f1, f2, f3,數據 a 通過管道 f3 得到結果 m,m。再通過管道 f2 得到結果 n,n 通過管道 f1 得到最終結果 b
fn = compose(f1, f2, f3)
b = fn(a)
函數組合
函數組合 (compose):如果一個函數要經過多個函數處理才能得到最終值,這個時候可以把中間過程的函數合併成一個函數。函數就像是數據的管道,函數組合就是把這些管道連接起來,讓數據穿過多個管道形成最終
函數組合默認是從右到左執行
函數的組合要滿足結合律 (associativity):
我們既可以把 g 和 h 組合,還可以把 f 和 g 組合,結果都是一樣的
lodash 中組合函數 flow() 或者 flowRight(),他們都可以組合多個函數
flow() 是從左到右運行
flowRight() 是從右到左運行,使用的更多一些
const _ = require('lodash')
const toUpper = s => s.toUpperCase()
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const f = _.flowRight(toUpper, first, reverse)
console.log(f(['one', 'two', 'three']))
模擬實現
const reverse = (arr) => arr.reverse();
const first = (arr) => arr[0];
const toUpper = (s) => s.toUpperCase();
// 從左往右執行就不需要執行reverse操作
function compose(...args) {
return function (value) {
return args.reverse().reduce(function (acc, fn) {
return fn(acc);
}, value);
};
}
const es6_Compose =
(...args) =>
(value) =>
args.reverse().reduce((acc, fn) => fn(acc), value);
const f = es6_Compose(toUpper, first, reverse);
console.log(f(["one", "two", "three"]));