1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        【翻譯】JavaScript 中的函數(shù)式編程:函數(shù)、組合與柯里化

        共 4157字,需瀏覽 9分鐘

         ·

        2021-01-21 08:16


        原文鏈接:https://blog.bitsrc.io/functional-programming-in-javascript-functions-composition-and-currying-3c765a50152e

        豆皮粉兒,又見面啦!今天字節(jié)跳動數(shù)據(jù)平臺的"陽羨"小哥哥給大家?guī)硪黄g文章"JavaScript 中的函數(shù)式編程:函數(shù)、組合與柯里化",干貨滿滿,不容錯(cuò)過?。?!

        本文作者:陽羨

        面向?qū)ο缶幊蘙1]和函數(shù)式編程[2]是兩種截然不同的編程范式,有各自的規(guī)則,也有各自的優(yōu)缺點(diǎn)。

        但是,JavaScript,并非一直使用一種編程范式,而是兼具兩者的特征。給你提供了普通 OOP 語言一些方面的能力,比如類、對象、繼承等。但同時(shí),它也給你提供了一些函數(shù)式概念,比如高階函數(shù),也提供了組合與柯里化的能力。

        高階函數(shù)

        先說說我在本文中涉及的三個(gè)概念中最重要的一個(gè):高階函數(shù)。

        擁有對高階函數(shù)的訪問權(quán)意味著函數(shù)不僅僅是一個(gè)你可以從代碼中定義和調(diào)用的構(gòu)造,事實(shí)上,你可以將它們作為可賦值的變量,即函數(shù)是一等公民。

        如果你寫過一些 JavaScript 的話,這一點(diǎn)應(yīng)該不會讓人感到驚訝,畢竟,你應(yīng)該已經(jīng)能夠簡單地從網(wǎng)上按照例子將匿名函數(shù)賦值給變量了。將函數(shù)賦值給變量在日常使用中并不罕見。

        如果 JavaScript 是你用來學(xué)習(xí)編程的第一門語言,那么你可能會對上述邏輯在許多其他語言中是無效的而感到驚訝。像賦值一個(gè)整數(shù)一樣賦值一個(gè)函數(shù)其實(shí)非常有用,事實(shí)上,本文所涉及的大部分主題都是由此衍生而來。

        高階函數(shù)的優(yōu)勢:封裝行為

        通過高階函數(shù),我們不僅可以像上面一樣給變量賦值函數(shù),而且,我們還可以在函數(shù)調(diào)用時(shí)將其作為參數(shù)傳遞。這又為創(chuàng)建動態(tài)邏輯打開了大門,你可以通過直接將復(fù)雜的行為作為參數(shù)傳遞來重用邏輯。

        想象一下,在一個(gè)純粹的面向?qū)ο蟮沫h(huán)境中工作,你需要重用一個(gè)邏輯,你知道一個(gè)基本邏輯可以被擴(kuò)展并作為復(fù)雜邏輯的一部分。在這種情況下,你可能會選擇使用繼承,通過將該邏輯封裝在一個(gè)抽象類中,然后將其擴(kuò)展為一組派生類,這些類利用該通用邏輯并對其進(jìn)行補(bǔ)充。這是完美而高效的 OOP 準(zhǔn)則,但讓我們看看我們剛才做了什么。我們:

        1. 創(chuàng)建了一個(gè)抽象的結(jié)構(gòu)來封裝我們的可重用邏輯。

        2. 創(chuàng)建了一個(gè)二級的派生類

        3. 讓后者在前者的基礎(chǔ)上進(jìn)行邏輯擴(kuò)展。

        但是,在函數(shù)式的環(huán)境下,為了復(fù)用邏輯,我們可以簡單地將需要復(fù)用的邏輯提取到一個(gè)函數(shù)中,然后將這個(gè)函數(shù)作為參數(shù)傳遞給任何其他可以從這種封裝行為中受益的函數(shù)。我們只是在創(chuàng)建函數(shù),而不是創(chuàng)建“模版”。

        下面的例子試圖展示我上面解釋的內(nèi)容。第一段代碼展示了你如何在 OOP 環(huán)境中去重用一個(gè)格式化并輸出的邏輯。

        然而,第二個(gè)例子表明,通過將邏輯提取出來,并使用函數(shù)封裝,你可以用很少的成本來創(chuàng)建你所需要的邏輯。你可以繼續(xù)添加更多的格式化( format)和輸出(output)函數(shù),然后只需用一行代碼將它們組合在一起就可以了。

        我的意思是,這兩種方法都有優(yōu)點(diǎn),而且都是非常高效的,沒有高低優(yōu)劣之分。函數(shù)式有多么令人難以置信的靈活性,以及我們?nèi)绾问褂没镜暮瘮?shù)式原理,這僅僅是因?yàn)槲覀冇心芰⑿袨椋春瘮?shù))作為參數(shù)傳遞,就好像它們是一個(gè)基本類型,如整數(shù)或字符串。

        高階函數(shù)的優(yōu)勢:整潔代碼

        整潔代碼的最好例子就是數(shù)組方法,如 forEach ,map ,reduce 等。在非函數(shù)式語言中,例如 C 語言,迭代一個(gè)數(shù)組的元素,并對它們進(jìn)行轉(zhuǎn)換,需要使用 for 循環(huán)或其他循環(huán)結(jié)構(gòu)。它們要求你以一種非常命令式的方式編寫代碼(換句話說,你需要表達(dá)事情如何在循環(huán)內(nèi)發(fā)生),而函數(shù)式則允許更多的聲明式編程風(fēng)格(你最終指定需要發(fā)生什么)。

        你的代碼實(shí)際上是在說:

        聲明一個(gè)新的變量 i 作為 myArray 的索引 它的值范圍從 0 到 myArray 的數(shù)組長度。

        遍歷i的值,然后把 i 位置的 myArray 的值乘 2,并將其添加到 transformedArray 數(shù)組中。

        它當(dāng)然是可行的,而且比較容易理解,但是,邏輯的復(fù)雜度會迅速升級,并且閱讀邏輯所需的認(rèn)知成本也會增加。然而,表達(dá)同樣的邏輯,函數(shù)式可能更具有可讀性:

        本質(zhì)上,這段代碼是說

        用 double 函數(shù)映射 (map) myArray 的元素,并將結(jié)果賦值給 transformedArray。

        因?yàn)檫壿嫳浑[藏在兩個(gè)函數(shù)(map和double)中,所以你不必?fù)?dān)心理解它們的工作原理。你也可以在第一個(gè)例子中把乘法邏輯隱藏在一個(gè)函數(shù)里面,但是仍然需要暴露迭代邏輯,晦澀的迭代邏輯是你作為一個(gè)閱讀代碼的人,必須在頭腦中解析以理解其工作原理的重要部分。

        柯里化(currying)

        函數(shù)柯里化是指將一個(gè)多參數(shù)的函數(shù)變成一個(gè)少參數(shù)的函數(shù),并將部分參數(shù)固定下來的編程思想。讓我用一個(gè)例子來解釋。

        現(xiàn)在,如果你想做的是將 10 加到一系列值上,你可以調(diào)用 add10,而不是每次都用相同的第二個(gè)參數(shù)調(diào)用 adder。我知道這可能是一個(gè)簡單的例子,當(dāng)你尋找柯里化時(shí),可能到處都是這個(gè)例子,但考慮到你正在做的事情:你正在利用 adder 函數(shù)的邏輯,并創(chuàng)建該函數(shù)的專門版本,換句話說,你正在擴(kuò)展該函數(shù),就像你使用一個(gè)類一樣。

        你可以把柯里化看作是函數(shù)式編程的繼承,按照這個(gè)思路,再回到上面格式化輸出的例子,你可以這樣編寫你的代碼:

        本質(zhì)上,你有一個(gè)叫做 log 的函數(shù),它需要三個(gè)參數(shù),而我們把它柯里化成專門的版本,只需要一個(gè),因?yàn)榱硗鈨蓚€(gè)已經(jīng)被我們選好了。

        需要注意的是,我把 log 函數(shù)當(dāng)作一個(gè)抽象類,只是因?yàn)樵谖业睦又校悴粫胫苯邮褂盟?,然而這樣做沒有任何限制,因?yàn)檫@只是一個(gè)普通的函數(shù)。如果我們使用的是抽象類,你就不能直接實(shí)例化它。

        組合(Composition)

        最后,函數(shù)組合是高階函數(shù)的另一個(gè)非常有趣的衍生品。乍一看,人們很容易把組成混淆為柯里化的情況,或者也許反過來說,有柯里化的函數(shù)而不是直接的值(就像我們在上面的記錄儀例子中做的那樣)可以被認(rèn)為是函數(shù)組合。

        這些觀點(diǎn)其實(shí)都沒有錯(cuò),當(dāng)你開始使用函數(shù)式時(shí),這兩個(gè)概念之間有一條非常細(xì)微的界限。具體來說,組成的定義如下:

        在計(jì)算機(jī)科學(xué)[3]中,函數(shù)組合是一種將簡單的函數(shù)[4]組合起來建立更復(fù)雜的函數(shù)的行為或機(jī)制。與數(shù)學(xué)[5]中通常的函數(shù)組合[6]一樣,每個(gè)函數(shù)的結(jié)果作為下一個(gè)函數(shù)的參數(shù)傳遞,最后一個(gè)函數(shù)的結(jié)果就是整個(gè)函數(shù)的結(jié)果。

        這是維基百科上關(guān)于函數(shù)組成的定義,最后加粗的部分是我強(qiáng)調(diào)的,因?yàn)槟鞘顷P(guān)鍵部分。在柯里化中,你沒有這個(gè)限制,你可以很容易地使用預(yù)先設(shè)定的函數(shù)參數(shù),如果它們是函數(shù),它們不必一個(gè)接一個(gè)地調(diào)用,讓第一個(gè)函數(shù)的結(jié)果成為第二個(gè)函數(shù)的輸入,以此類推。

        與柯里化不同,這是一個(gè)強(qiáng)大的工具,因?yàn)樵谶@里,只有部分功能,每個(gè)功能都在完成一個(gè)特定的任務(wù),等待著被組成更大更復(fù)雜的東西。想想看,就好像函數(shù)是樂高積木一樣,通過組合,只要你把正確的邏輯柯里化,并以正確的順序組合在一起(即只要你以正確的順序組合成正確的函數(shù)),你就能創(chuàng)造出任何你能想到的東西。

        如果你以前使用過 Linux 發(fā)行版,你可能已經(jīng)注意到,Linux 中的 CLI 工具遵循一個(gè)非常確定的模式:它們只做一件事,并且能夠從標(biāo)準(zhǔn)輸入中讀取結(jié)果,并將其輸出到標(biāo)準(zhǔn)輸出。因此,允許用戶將多個(gè)命令組合成一句功能強(qiáng)大的命令,例如:

        1. $ cat myfile.txt | wc -l

        如上所示,我是在讀取一個(gè)文件,并計(jì)算它的行數(shù),然而,如果以不同的方式或與其他命令組合,輸出可能會有很大的不同。同樣的情況也發(fā)生在函數(shù)上,如果你在設(shè)計(jì)函數(shù)的時(shí)候,讓一個(gè)函數(shù)的輸出可以成為另一個(gè)函數(shù)的輸入,你也可以像這樣組合它們。

        看看上面和最后的例子,我創(chuàng)建了四個(gè)不同的處理字符串的函數(shù),并將它們組合成三個(gè)不同的函數(shù)。你可以通過組合函數(shù)來創(chuàng)建新函數(shù)。這就是組合的魅力所在。

        仔細(xì)看一下代碼,有幾處值得關(guān)注。

        • 有些函數(shù)(replace和findMatches)實(shí)際上是接受參數(shù)并返回一個(gè)函數(shù)。這是為了使它們更通用,由于JS將返回的函數(shù)的上下文與函數(shù)本身一起保存(即閉包),我們能夠?qū)⑦@些參數(shù)作為被返回函數(shù)的 "全局 "變量,并作為組合的一部分使用。

        • 請注意 compose 函數(shù),它是利用 ES6 的結(jié)構(gòu)操作符,簡單地在函數(shù)參數(shù)上進(jìn)行迭代,并執(zhí)行它們,然后將其結(jié)果發(fā)送給下一個(gè)函數(shù)。reduceRight 的使用保證了我們在函數(shù)列表中從右到左進(jìn)行執(zhí)行,這就是為什么我總是把小寫字母加到最后一個(gè)。如果你想讓順序反過來,你可以直接使用 reduce 來代替。

        結(jié)論

        如果使用得當(dāng),高階函數(shù)以及柯里化和組合都是非常強(qiáng)大的工具。我知道,如果你不習(xí)慣于使用函數(shù)式的思維模式,而是更愿意使用類和對象,這些技術(shù)可能看起來有悖于直覺,但它們本質(zhì)上并不晦澀難懂,只是需要換個(gè)角度去思考。

        感受并享受 JavaScript 的函數(shù)式編程吧!

        你之前使用過這些工具嗎?你更喜歡用函數(shù)式編程思想寫代碼嗎?還是你更喜歡 OOP 開發(fā)?歡迎在評論區(qū)發(fā)表你的看法。

        感謝閱讀,我們下期再見!


        References

        [1]?面向?qū)ο缶幊??https://en.wikipedia.org/wiki/Object-oriented_programming
        [2]?函數(shù)式編程:?https://en.wikipedia.org/wiki/Functional_programming
        [3]?計(jì)算機(jī)科學(xué):?https://en.wikipedia.org/wiki/Computer_science
        [4]?函數(shù):?https://en.wikipedia.org/wiki/Subroutine
        [5]?數(shù)學(xué):?https://en.wikipedia.org/wiki/Mathematics
        [6]?函數(shù)組合:?https://en.wikipedia.org/wiki/Function_composition

        瀏覽 38
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            午夜黄色福利 | 精品人妻伦一二三区蜜桃 | 理论片免费一区 | 嗯嗯啊啊在线视频 | 色阁阁 | 依人成人| 天天操天天看 | 三个黑人大屌干欧美女孩 | 骚逼无码 | 激情婷婷丁香花 |