最近在學(xué)習(xí)函數(shù)式編程,記錄了一些筆記岁忘,也總結(jié)了一些自己的理解辛慰。我準(zhǔn)備整理一下陸陸續(xù)續(xù)發(fā)出來,本文不算是一篇文章吧干像,算是自己在學(xué)習(xí)函數(shù)式編程中的一些總結(jié)帅腌,也算一個(gè)引子。
一些約束
- 不要為了延遲執(zhí)行麻汰,而簡(jiǎn)單地用一個(gè)函數(shù)把另一個(gè)函數(shù)包起來速客。
- 參數(shù)命名的時(shí)候,不要把參數(shù)名限制在特定的數(shù)據(jù)上五鲫,容易造成重復(fù)造輪子溺职。
- 函數(shù)不依賴外部值
純函數(shù)
1.概念
函數(shù)式編程中函數(shù)是一等公民,即普通人。對(duì)于函數(shù)浪耘,強(qiáng)調(diào)純函數(shù)的概念智亮。什么是純函數(shù)呢?
純函數(shù)是這樣一種函數(shù)点待,相同的輸入阔蛉,永遠(yuǎn)會(huì)得到相同的輸出,而且沒有任何可觀察的副作用癞埠。
比較明顯的一個(gè)例子就是slice和splice状原。前者純而后者不純,想想為什么~
這里說的沒有副作用具體是什么呢苗踪?像上面我提到的splice颠区,它的副作用就是修改了本來的數(shù)據(jù),還有一些容易想到的情況通铲,如向數(shù)據(jù)庫插入了數(shù)據(jù)毕莱,打印了數(shù)據(jù),獲取了用戶輸入等颅夺。 總之朋截,就是一切跟函數(shù)外部環(huán)境反生交互的行為。
歸根結(jié)底吧黄,這些行為很容易導(dǎo)致“相同的輸入返回相同的結(jié)果”這一概念的失效部服。這也是我們盡量避開它們的原因。
2.為什么追求純拗慨?
為什么要花這么多力氣去實(shí)現(xiàn)純函數(shù)呢廓八,可見的幾點(diǎn)好處如下:
可緩存性(Cacheable)
重復(fù)的計(jì)算不需要多次計(jì)算,這就是可緩存性赵抢。
let addFive = memoize(x=>x+5)
addFive(1);
addFive(1);
addFive(1);
//真正的計(jì)算只會(huì)發(fā)生一次剧蹂。
memoize的實(shí)現(xiàn)很簡(jiǎn)單,使用一個(gè)對(duì)象來存儲(chǔ)計(jì)算過的值即可烦却。下面是一個(gè)簡(jiǎn)單的實(shí)現(xiàn)
const momize=(func)=>{
//cache對(duì)象用于存儲(chǔ)計(jì)算過的值
let cache={}
return ()=>{
let key = JSON.stringify(arguments)
if(!cache[key]){
cache[key] = func.apply(this,arguments)
}
return cache[key]
}
}
可移植性(Portable)
純函數(shù)的依賴很明確宠叼,需要的數(shù)據(jù)都在參數(shù)中體現(xiàn)了。這樣做短绸,使應(yīng)用更加靈活车吹。因?yàn)橐磺械囊蕾嚩紖?shù)化了,當(dāng)依賴變化時(shí)醋闭,直接把新的依賴傳遞進(jìn)去就好了窄驹。
可測(cè)試性(Testable)
這也是可以預(yù)見的,相同的輸入具有相同的輸出证逻。意味著測(cè)試時(shí)我們只需要給函數(shù)一個(gè)輸入乐埠,然后斷言它的輸出即可。
引用透明(referential transparency)
函數(shù)的返回值只依賴于它的輸入,這就是引用透明性丈咐。很明顯瑞眼,純函數(shù)具有這個(gè)特性。這個(gè)特性可以幫助我們更好的分析我們的程序棵逊。
并行
純函數(shù)可以任意并行的運(yùn)行伤疙。因?yàn)榧兒瘮?shù)沒有副作用,不會(huì)和其它純函數(shù)進(jìn)入競(jìng)爭(zhēng)狀態(tài)辆影。也不需要訪問共享的內(nèi)存徒像。
柯里化
對(duì)于函數(shù)式編程來說,柯里化是一個(gè)不可或缺的工具蛙讥。它的概念很簡(jiǎn)單锯蛀,只傳遞給函數(shù)一部分參數(shù)來調(diào)用它,返回一個(gè)接受剩下參數(shù)的函數(shù)次慢。
一個(gè)被舉得最多的例子:
const addFunc =(a,b,c,d)=>{
return a+b+c+d;
}
const add = curry(addFunc)
add(1)(2)(3)(4) //10
add(1,2)(3,4) //10
add(1,2,3,4) //10
經(jīng)過柯里化旁涤,現(xiàn)在我們不需要再一次性的將所有參數(shù)傳入了。
代碼組合(compose)
之前我有一篇文章分析過redux的源碼迫像,其中有一個(gè)文件compose.js:
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
這就是組合函數(shù)劈愚,一個(gè)函數(shù)的返回將作為另一個(gè)函數(shù)的輸入。
PS:這個(gè)代碼是從左往右運(yùn)行的侵蒙,函數(shù)式推薦從右向左運(yùn)行造虎。據(jù)說這樣更加能夠反映數(shù)學(xué)上的含義。
通過組合纷闺,我們可以像搭積木一樣把簡(jiǎn)單的功能拼湊成復(fù)雜的功能。
pointfree
這是一種風(fēng)格份蝴,指的是數(shù)據(jù)無關(guān)犁功,就像上面的compose,舉個(gè)例子:
//非pointfree 風(fēng)格
let upperReplace=(arg)=>{
return arg.toUpperCase().replace(/\s+/ig/,'_')
}
//pointfree風(fēng)格
let upperReplace = compose(replace(/\s+/ig/,'_'),toUpperCase)
非常明顯婚夫,非pointfree模式提到了數(shù)據(jù)arg浸卦,而pointfree沒有“覆冢可以看出pointfree還能幫我們減少不必要的命名限嫌,我相信你們也和我一樣覺得命名是一件頭疼的事。
debug
使用組合有時(shí)容易出錯(cuò)时捌,比如參數(shù)個(gè)數(shù)對(duì)應(yīng)不上之類的怒医。對(duì)于組合有一種比較實(shí)用但不純的方法來追蹤代碼的執(zhí)行情況。
let trace = curry((tag,x)=>{
console.log(tag,x);
return x;
})
//將trace放在合適的地方奢讨,就可以看到該處的日志稚叹,從而糾錯(cuò)
let test = compose(otherOper,trace("after toUpper"),toUpper,firstEle,reverse)