函數(shù)式編程學(xué)習(xí)過程中的一些總結(jié)

最近在學(xué)習(xí)函數(shù)式編程,記錄了一些筆記,也總結(jié)了一些自己的理解。我準(zhǔn)備整理一下陸陸續(xù)續(xù)發(fā)出來,本文不算是一篇文章吧,算是自己在學(xué)習(xí)函數(shù)式編程中的一些總結(jié),也算一個(gè)引子。

一些約束

  • 不要為了延遲執(zhí)行,而簡單地用一個(gè)函數(shù)把另一個(gè)函數(shù)包起來。
  • 參數(shù)命名的時(shí)候,不要把參數(shù)名限制在特定的數(shù)據(jù)上,容易造成重復(fù)造輪子。
  • 函數(shù)不依賴外部值

純函數(shù)

1.概念

函數(shù)式編程中函數(shù)是一等公民,即普通人。對于函數(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)很簡單,使用一個(gè)對象來存儲(chǔ)計(jì)算過的值即可。下面是一個(gè)簡單的實(shí)現(xiàn)


const momize=(func)=>{
    //cache對象用于存儲(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)去就好了。

可測試性(Testable)

這也是可以預(yù)見的,相同的輸入具有相同的輸出。意味著測試時(shí)我們只需要給函數(shù)一個(gè)輸入,然后斷言它的輸出即可。

引用透明(referential transparency)

函數(shù)的返回值只依賴于它的輸入,這就是引用透明性。很明顯,純函數(shù)具有這個(gè)特性。這個(gè)特性可以幫助我們更好的分析我們的程序。

并行

純函數(shù)可以任意并行的運(yùn)行。因?yàn)榧兒瘮?shù)沒有副作用,不會(huì)和其它純函數(shù)進(jìn)入競爭狀態(tài)。也不需要訪問共享的內(nèi)存。

柯里化

對于函數(shù)式編程來說,柯里化是一個(gè)不可或缺的工具。它的概念很簡單,只傳遞給函數(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é)上的含義。
通過組合,我們可以像搭積木一樣把簡單的功能拼湊成復(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ù)對應(yīng)不上之類的。對于組合有一種比較實(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)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容