今天主要想和你們分享一下函數組合Function Composition
的概念以及一些實踐。函數組合應該是函數式編程中最重要的幾個概念之一了~ 因此如下的學習內容十分重要~git
備註:若是對接下來使用的partial
& curry
的概念不熟悉,能夠回去看個人第一篇有介紹哦~函數式編程入門實踐 —— Partial/Curry。(由於接下來就會用到)github
compose
若是咱們想,對一個值執行一系列操做,並打印出來,考慮如下代碼:編程
import { partial, partialRight } from 'lodash';
function add(x, y) {
return x + y;
}
function pow(x, y) {
return Math.pow(x, y);
}
function double(x) {
return x * 2;
}
const add10 = partial(add, 10);
const pow3 = partialRight(pow, 3);
console.log(add10(pow3(double(2)))); // 74
複製代碼
備註:partialRight
和partial
見名知意,至關因而彼此的鏡像函數。redux
_.partialRight: This method is like _.partial except that partially applied arguments are appended to the arguments it receives.數組
無需否定,這段示例代碼的確毫無心義。可是爲了達成這一系列操做,我最終執行了這一長串嵌套了四層的函數調用:console.log(add10(pow3(double(2))))
。(說實話,個人確以爲有點難以閱讀了...),若是更長了,怎麼辦?可能有的同窗會給出如下答案:bash
function mixed(x) {
return add10(pow3(double(2)))
}
console.log(mixed(2)); // 74
複製代碼
的確,看似好了點,可是也只是將這個冗長的調用封裝了一下而已。會不會有更好的作法?app
function compose(...args) {
return result => {
const funcs = [...args];
while(funcs.length > 0) {
result = funcs.pop()(result);
}
return result;
};
}
compose(console.log, add10, pow3, double)(2) // 74
複製代碼
歐耶!咱們經過實現了一個簡單的compose
函數,而後發現調用的過程compose(console.log, add10, pow3, double)(2)
居然變得如此優雅!多個函數的調用從代碼閱讀上,多層嵌套被拍平變成了線性!(固然實際上本質上仍是嵌套的函數調用的)。koa
固然,關於compose
的更加函數式的實現以下:函數式編程
function compose(...funcs) {
return result => [...funcs]
.reverse()
.reduce((result, fn) => fn(result), result);
}
複製代碼
那麼有同窗可能也發現了,上述compose
以後的函數是隻能夠傳遞一個參數的。這無疑顯得有點蠢?難道不能夠優化實現支持多個參數麼?函數
考慮如下代碼:
function compose(...funcs) {
return funcs
.reverse()
.reduce((fn1, fn2) => (...args) => fn2(fn1(...args)));
}
複製代碼
細心觀察,經過將參數傳遞進行懶執行,從而巧妙的完成了這個任務!示例以下:
function multiply(x, y) {
return x * y;
}
compose(
console.log, add10, pow3, multiply
)(2, 5); // 1010
複製代碼
固然上述代碼最終也能夠這麼寫:
compose(
console.log,
partial(add, 10),
partialRight(pow, 3),
partial(multiply, 5)
)(2); // 1010
複製代碼
pipe
那麼學習完了compose
,pipe
又是什麼呢?首先在剛剛學習compose
函數時,可能有同窗會以爲有點小別扭,由於compose
從左到右傳遞參數的順序恰好和調用順序相反的!
(固然若是說幫助理解記憶的話,compose傳參的順序就是咱們書寫函數嵌套的順序,在腦海裏把console.log(add10(pow3(double(2))))
這一長串裏的括號去了,是否是就是參數的順序了~)
回到話題,pipe
是什麼?同窗們有沒有使用過命令行,好比我經常使用的一個命令,將當前工做路徑拷貝到剪切板,隨時ctrl + v就可使用了~
pwd | pbcopy
複製代碼
固然我木有走題!注意以上的管道符 |
,這個其實就是pipe
,能夠將數據流從左到右傳遞。
考慮示例代碼以下:
function pipe(...args) {
return result => {
const funcs = [...args];
while(funcs.length > 0) {
result = funcs.shift()(result);
}
return result;
};
}
pipe(
partial(multiply, 5),
partialRight(pow, 3),
partial(add, 10),
console.log
)(2); // 1010
複製代碼
等等,從左到右?好像和compose恰好相反誒!並且這段代碼好眼熟啊!將pop方法換成了shift方法而已!
那麼實際上等價於:
const reverseArgs = func => (...args) => func(...args.reverse());
const pipe = reverseArgs(compose);
複製代碼
哈咱們避免了重複無心義的代碼!固然不管是compose
仍是pipe
,本質上咱們都將命令式的代碼轉換成了聲明式的代碼,對一個值的操做能夠理解爲值在函數之間流動
2 --> multiply --> pow --> add --> console.log
多麼優雅呀~
遞歸版本的compose
本質上更接近概念,可是可能也會讓人難以理解。瞭解一下也不錯~
代碼以下:
function compose(...funcs) {
const [fn1, fn2, ...rest] = funcs.reverse();
function composed(...args) {
return fn2(fn1(...args));
};
if (rest.length === 0) return composed;
return compose(
...rest.reverse(),
composed
);
}
複製代碼
redux以及koa其實都是有中間件的思想,組合中間件的compose原理和上述代碼也相差不遠。你們能夠稍微閱讀如下兩個連接的源碼,代碼都很簡短,但都驗證了compose的概念只要在實際開發中運用得當的話,能夠發揮強大的魔力!
因此,學習函數式編程並非讓本身看起來有多麼聰明,也不是爲了迷惑隊友(哈哈),也不是單純爲了學習而學習。它的實際意義在於,給函數調用穿上語義化的衣服,讓實際的應用代碼最終更可讀友好,利於維護~ 固然與此同時,也會訓練本身寫出聲明式的代碼。
(話說React Hooks和FP很搭配啊~ 過段時間也想在這個話題上分享一下)
謝謝你們(●´∀`●)~