最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊(cè)

瞎話 JavaScript 函數(shù)式:add(3)(4)一點(diǎn)都不酷

2023-06-22 22:14 作者:貪玩魚(yú)翅  | 我要投稿

本文首發(fā)于個(gè)人博客網(wǎng)站?https://xiongyuchi.top

這可能是一篇很無(wú)聊的文章,它無(wú)聊就無(wú)聊在或許這個(gè)世界真的需要這樣的文章。

國(guó)際慣例,寫在前面


作為一名野雞大學(xué)畢業(yè)的野路子程序員,第一次聽(tīng)說(shuō)函數(shù)式編程這個(gè)概念的時(shí)候我大概已經(jīng)工作一年了。彼時(shí)的我正在做 Ruby&JavaScript 全棧工程師的美夢(mèng)。


一名有抱負(fù)(暫時(shí))的工程師通常不會(huì)放過(guò)任何一個(gè)知識(shí)盲點(diǎn),于是我立刻打開(kāi)某書(shū)某乎某 SDN 找來(lái)了幾篇文章學(xué)習(xí)。


當(dāng)柯里化、純函數(shù)、高階函數(shù)等一個(gè)個(gè)概念出現(xiàn)在我的眼前時(shí),我看的如癡如醉云里霧里,不久之后我就把這些概念完全拋在了腦后。


某天下午我在一篇文章里我看到了如下這樣一段代碼,我忽而意識(shí)到我不能繼續(xù)這樣自己糊弄自己了。



我想大部分有過(guò)一點(diǎn) JavaScript 開(kāi)發(fā)經(jīng)驗(yàn)的開(kāi)發(fā)者都能看得出這段代碼的問(wèn)題,一方面這段代碼如此抽象的實(shí)現(xiàn)了一個(gè)判斷奇偶數(shù)的功能頗有高射炮打蚊子的感覺(jué),另一方面相互遞歸無(wú)疑是低效且危險(xiǎn)的。


光陰任然,我在龜速學(xué)習(xí)的道路上一轉(zhuǎn)眼又混過(guò)去兩年,當(dāng)有一天我又看到一篇文章里看到了如下代碼塊的時(shí)候(請(qǐng)注意,下面的代碼塊是我從原文抄過(guò)來(lái)的,包括注釋),我知道有些事情不能再拖了。



函數(shù)式編程從哪兒來(lái)?


提到函數(shù)式編程,不得不提的是函數(shù)式編程的鼻祖——Lambda 演算法。


考慮到 Lambda 演算法可能并不是一個(gè)人盡皆知的概念,我們將要提到同一時(shí)期的另一個(gè)概念——圖靈機(jī),相信這樣就有部分人會(huì)稍微明白些了。


簡(jiǎn)單來(lái)說(shuō),圖靈機(jī)是一種理論上的計(jì)算模型,用大白話說(shuō)就是它假設(shè)了一臺(tái)機(jī)器,而這臺(tái)機(jī)器最大的特點(diǎn)是任何可以用科學(xué)計(jì)算解決的問(wèn)題都可以用它來(lái)解決(也就是說(shuō)顯然也存在不可以用科學(xué)計(jì)算解決的問(wèn)題,此時(shí)圖靈機(jī)亦無(wú)能為力)。


圖靈機(jī)是存在于假象之中的機(jī)器,因?yàn)槭澜缟喜⒉淮嬖跓o(wú)限長(zhǎng)的紙帶。但與之同時(shí),圖靈機(jī)又明確了這樣一臺(tái)機(jī)器應(yīng)該具備的最基本的特性,上世紀(jì)的計(jì)算機(jī)科學(xué)家們就是基于這些特性才發(fā)明了計(jì)算機(jī)。


本質(zhì)上,Lambda 演算法與圖靈機(jī)有著同樣的目的,我們的祖師爺艾倫·圖靈(Alan Turing)已經(jīng)證明了 Lambda 演算法與圖靈機(jī)在計(jì)算能力上的等價(jià)性(在 Lambda 演算法中模擬了圖靈機(jī)的行為),而這一結(jié)論有一個(gè)更為人盡皆知的名字——圖靈等價(jià)。


正是因?yàn)榈葍r(jià)性的存在,即便當(dāng)今的計(jì)算機(jī)大多都是基于圖靈機(jī)實(shí)現(xiàn)的,依然不妨礙我們實(shí)現(xiàn)大量的繼承了 Lambda 演算法思想的編程語(yǔ)言,這些語(yǔ)言正是我們今天所說(shuō)的函數(shù)式編程語(yǔ)言。


Lambda 演算法定義了什么?


不同于圖靈機(jī),Lambda 演算法并不是一臺(tái)機(jī)器,而是一種數(shù)學(xué)模型,是一堆數(shù)學(xué)公式。


一個(gè)通用的計(jì)算模型要通過(guò)有限的手段來(lái)抽象無(wú)限的問(wèn)題,Lambda 演算法通過(guò)函數(shù)實(shí)現(xiàn)了這種抽象能力。事實(shí)上 Lambda 演算法里除了函數(shù)就沒(méi)有別的東西了。


把 Lambda 演算法推一遍不是本文的目的(真要那樣放一個(gè) Wiki 的鏈接就完事了,再說(shuō)我也推不出來(lái)。


阿隆佐·丘奇(Alonzo Church)通過(guò)一系列的參數(shù)傳遞與消除,實(shí)現(xiàn)了對(duì)數(shù)字、運(yùn)算以及程序語(yǔ)言三種基本結(jié)構(gòu)(順序、分支、循環(huán))的抽象,其中關(guān)于數(shù)字的抽象如今被稱為丘奇數(shù),這些內(nèi)容看起來(lái)并不容易理解,下面是從 Google 上抄過(guò)來(lái)的一部分,能看明白定義出來(lái)的是個(gè)什么東西,以及怎么被應(yīng)用到后續(xù)的運(yùn)算中就夠了,感興趣的同學(xué)可以自行搜索關(guān)鍵字并深入了解。



很容易發(fā)現(xiàn)上述代碼具備如下幾個(gè)特點(diǎn):


1. 所有函數(shù)都只有一個(gè)參數(shù)

2. 函數(shù)可以作為函數(shù)的參數(shù)和返回值

3. 純粹的數(shù)學(xué)推演,不會(huì)修改一個(gè)已有的值


如果你已經(jīng)看過(guò)一些函數(shù)式編程的文章了,那么你應(yīng)該已經(jīng)發(fā)現(xiàn)了,上述分別對(duì)應(yīng)了柯里化、函數(shù)一等公民以及不可變性這些概念。實(shí)際上如果更進(jìn)一步的講,如今談?wù)摵瘮?shù)式編程提到的這些概念均是來(lái)源于此,這是它們的因果。


當(dāng)然,要實(shí)現(xiàn)完整的運(yùn)算與三種結(jié)構(gòu)并不是那么簡(jiǎn)單,這里特地把循環(huán)拿出來(lái)過(guò)一下。


要通過(guò)函數(shù)實(shí)現(xiàn)一個(gè)循環(huán)結(jié)構(gòu)并不困難,相信大家都能在第一時(shí)間想到遞歸,如今在面試的時(shí)候也經(jīng)常會(huì)遇到諸如「不使用遞歸實(shí)現(xiàn) deepClone」這樣的問(wèn)題,考察的就是這個(gè)。


一個(gè)常見(jiàn)的的遞歸模板如下:


然鵝,在數(shù)學(xué)推演的世界里,在一個(gè)函數(shù)定義完成前將其應(yīng)用到自身的運(yùn)算中的行為并不嚴(yán)謹(jǐn)(當(dāng)然,我并不確定這是不是主因,歡迎補(bǔ)充),因而 Lambda 演算法使用了一種特別的技巧來(lái)實(shí)現(xiàn)遞歸,這種技巧有一個(gè)很酷一聽(tīng)就讓人想融資的名字——Y Combinator(Y 組合子)。


關(guān)于 Y 組合子這里不過(guò)多展開(kāi)了,大致的思路是把函數(shù)的定義和調(diào)用分離,感興趣可以看文末附加的參考文章。


很多人會(huì)困惑上述是一坨什么東西,這樣復(fù)雜的結(jié)構(gòu)看起來(lái)并沒(méi)有比如今面向過(guò)程,面向?qū)ο蟮拇a更加易讀和易維護(hù)。


然而如果是跟著全文走的同學(xué)應(yīng)該還記得 Lambda 演算法的目的是什么?;剡^(guò)頭來(lái)我們發(fā)現(xiàn),現(xiàn)在 Lambda 演算法確實(shí)做到了僅通過(guò)寥寥無(wú)幾的幾個(gè)特征(單參數(shù),函數(shù)一等公民,函數(shù)定義等)實(shí)現(xiàn)了一個(gè)通用計(jì)算模型所必備的所有特性,這也就意味著,Lambda 演算法是完全可以用來(lái)實(shí)現(xiàn)一臺(tái)通用計(jì)算機(jī)的。


這個(gè)結(jié)論在今天有另一個(gè)詞來(lái)描述——圖靈完備。


函數(shù)式 !== 函數(shù)式


事情并沒(méi)有就此結(jié)束,Lambda 演算法復(fù)雜的數(shù)學(xué)推演讓人望而卻步,實(shí)際上在使用一門基于 Lambda 演算法設(shè)計(jì)的編程語(yǔ)言開(kāi)發(fā)程序時(shí)并不一定要從定義數(shù)字/運(yùn)算/結(jié)構(gòu)開(kāi)始,正如我們?cè)趫D靈機(jī)上實(shí)現(xiàn)的編程語(yǔ)言亦不需要從紙帶打孔開(kāi)始定義程序一樣。


在 Lambda 演算法的基礎(chǔ)上人們發(fā)明了一系列的編程語(yǔ)言如最為著名的 Lisp 語(yǔ)言家族。這些語(yǔ)言共同繼承了 Lambda 演算法的思想,其特點(diǎn)是通過(guò)函數(shù)來(lái)實(shí)現(xiàn)對(duì)問(wèn)題的抽象,由此形成了一種特有的編程范式。


多啰嗦幾句,編程范式是編程語(yǔ)言用于抽象并解決現(xiàn)實(shí)問(wèn)題的通用方法,以大家熟識(shí)可能也不見(jiàn)得熟識(shí)的面向?qū)ο缶幊虨槔?,在面向?qū)ο缶幊讨?,我們通常把事物抽象成類與對(duì)象來(lái)對(duì)現(xiàn)實(shí)問(wèn)題的建模,在這個(gè)過(guò)程中我們需要考慮類,對(duì)象,屬性甚至方法之間的組合與復(fù)用邏輯,因此有了如繼承、多態(tài)、封裝,接口,抽象類等一系列的概念,這些概念均是編程范式的一部分。


同理可得,函數(shù)式編程是通過(guò)函數(shù)來(lái)抽象現(xiàn)實(shí)問(wèn)題的通用程序設(shè)計(jì)方法,而高階函數(shù),compose 等正是函數(shù)式編程中用于組合與復(fù)用函數(shù)這一單元的概念。


時(shí)至今日,函數(shù)式編程語(yǔ)言百花齊放,各種各樣的特性被加入到了函數(shù)式編程語(yǔ)言中,這些特性有的是為了性能,有的是為了開(kāi)發(fā)體驗(yàn),還有的是為了可讀性。這些特性并不是 Lambda 演算法所必須的,甚至有些特性是從其它編程范式中借鑒過(guò)來(lái)的,一方面這使得廣義上的函數(shù)式編程與狹義上的函數(shù)式編程有了區(qū)別,另一方面也使得函數(shù)式編程語(yǔ)言的特性更加豐富,更加適合實(shí)際開(kāi)發(fā),事實(shí)上當(dāng)前有很多流行的通用編程語(yǔ)言都結(jié)合了多種編程范式,JavaScript 便是其中之一。


所以學(xué)習(xí)函數(shù)式到底是學(xué)什么


在學(xué)習(xí)一門范式時(shí)對(duì)范式擁有一個(gè)正確的認(rèn)知非常重要。從圖靈等價(jià)性上我們很容易得出函數(shù)式編程與面向?qū)ο缶幊淌堑葍r(jià)的這樣的結(jié)論,這也就意味著并不存在函數(shù)式編程可以解決而面向?qū)ο缶幊虩o(wú)法解決的問(wèn)題,亦不存在誰(shuí)比誰(shuí)高級(jí)的說(shuō)法。


在嘗試了解函數(shù)式編程到底是什么的過(guò)程中,我曾接連嘗試閱讀了《計(jì)算機(jī)程序的構(gòu)造和解釋》(SICP)和《計(jì)算的本質(zhì)》兩本書(shū)但最后都以失敗告終。


之后因機(jī)緣巧合大致翻閱了《程序設(shè)計(jì)方法》(HTDP)一書(shū)的前十幾章,這是一本使用 Racket 語(yǔ)言描述的書(shū),Racket 是 Lisp 大家族下的一門編程語(yǔ)言,亦是純函數(shù)式編程語(yǔ)言。有趣的地方在于這本書(shū)從頭到尾都沒(méi)有提到函數(shù)式編程這個(gè)詞,在那之后我又看了 UCB CS61A 關(guān)于函數(shù)抽象的部分,同樣也沒(méi)有提到柯里化、純函數(shù)等亂七八糟的概念。


如今看來(lái)我認(rèn)為這些才是真正能夠教會(huì)你函數(shù)式編程的東西,函數(shù)式編程并不是復(fù)雜概念的集合,它只是一種協(xié)助你編寫代碼的方式罷了,在理解了它的本質(zhì)以后,剩下的就只是平平凡凡的寫代碼而已了。如果不能在一開(kāi)始就有這種認(rèn)知,很有可能在學(xué)習(xí)的過(guò)程中走火入魔。


印象里好像《Ruby 元編程》中有一句非常經(jīng)典的話:根本沒(méi)有什么元編程,只有編程,我想函數(shù)式編程大概亦是如此。


對(duì)中文互聯(lián)網(wǎng)的環(huán)境多少是有點(diǎn)怨念的,當(dāng)我們使用搜索引擎搜索「JavaScript 函數(shù)式編程」會(huì)找到大量的文章,這些文章與其說(shuō)在講如何使用 JavaScript 進(jìn)行函數(shù)式編程,不如說(shuō)是在講如何使用 JavaScript 實(shí)現(xiàn) Lambda 演算法。


而當(dāng)搜索柯里化的應(yīng)用時(shí)就更有趣了,大量的文章在講如何實(shí)現(xiàn)一個(gè) add(3)(4) 的函數(shù),最后的結(jié)論無(wú)非是惰性求值和參數(shù)復(fù)用等概念,卻對(duì)這樣做的性能開(kāi)銷只字不提。就更少有人提及最初的 Lambda 演算法本身就只支持一個(gè)參數(shù)這件事情了。


Lambda 只使用一個(gè)參數(shù)就實(shí)現(xiàn)了對(duì)問(wèn)題的抽象,因而完全不需要支持多個(gè)參數(shù),在編程語(yǔ)言為了表達(dá)能力而支持多個(gè)參數(shù)的今天反而柯里化回去強(qiáng)行使用一個(gè)參數(shù)真的是有意義的嗎?


相比之下另外一些文章堪稱扭曲事實(shí),以純函數(shù)無(wú)副作用的線程安全性出發(fā),最終得到了一個(gè)函數(shù)式編程比面向?qū)ο缶幊谈呒?jí)的結(jié)論。另一側(cè)還有人完全忽視函數(shù)式編程的抽象程度,將其描述為一種“有水平的程序員都在業(yè)務(wù)開(kāi)發(fā)里大量用到”的范式,如果 Bilibili 的搜索排名還沒(méi)有變化,那么我很推薦大家去試試搜索「函數(shù)式編程」,看看第一個(gè)視頻的標(biāo)題。后來(lái)我點(diǎn)開(kāi)視頻看了大概 10 秒以后終于理解了所謂不可描述之事,流量密碼都讓你們懂完了,當(dāng)個(gè)人吧。


最后,多范式的 JavaScript


讓我們把目光再次回到 JavaScript 上來(lái),JavaScript 是一門多范式的編程語(yǔ)言,它既可以面向?qū)ο?,也可以面向過(guò)程,同時(shí)也可以函數(shù)式。


大家所熟知的工具庫(kù)如 Lodash 在使用方法上有著相當(dāng)?shù)暮瘮?shù)式的影子:



多范式語(yǔ)言的價(jià)值在于它可以讓我們?cè)诮鉀Q問(wèn)題時(shí)更加靈活,在《JavaScript 設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐》一書(shū)中,作者曾經(jīng)多次使用面向?qū)ο笈c函數(shù)式兩種范式來(lái)實(shí)現(xiàn)同一種模式,而事實(shí)上相比于面向?qū)ο螅瘮?shù)式的實(shí)現(xiàn)更加簡(jiǎn)潔,更加優(yōu)雅,也更加符合大家對(duì) JavaScript 的印象。


碎碎念和疊 Buff 環(huán)節(jié)


本文不針對(duì)任何人,也不針對(duì)任何文章,只是想說(shuō)說(shuō)我自己的看法。


正如上一節(jié)所說(shuō),JavaScript 是一門多范式的編程語(yǔ)言,早年間受到 Java 的影響,大家熱衷于照搬 Java 的面向?qū)ο髮?shí)踐,在近幾年隨著 React 對(duì)函數(shù)式編程的推崇,函數(shù)式編程又漸漸成為了大家的熱點(diǎn)。


坦白說(shuō)我想寫這篇文章很久了,曾有一段時(shí)間非常擔(dān)心自己對(duì)函數(shù)式的理解可能并不全面甚至正確而遲遲不敢動(dòng)筆,但轉(zhuǎn)念一想人家 add(3)(4) 都有人寫還有人點(diǎn)贊我有啥不敢寫的,笑死。


本文的初版是一篇更有戾氣的文章,但冷靜下來(lái)還是覺(jué)得糾正更重要。于我個(gè)人,于我身邊的很多朋友的成長(zhǎng)經(jīng)歷來(lái)看,大家都有過(guò)被那些文章忽悠瘸了的過(guò)往,再說(shuō)這互聯(lián)網(wǎng)似乎從來(lái)都不缺戾氣。


最后的最后,感謝能看到這里的你,如果文中有什么錯(cuò)誤或者不妥的地方,歡迎指正。


參考


- [Lambda 演算科普系列](https://www.bilibili.com/video/BV1pU4y1v7Hj/?spm_id_from=333.337.search-card.all.click):winter 老師在 Bilibili 關(guān)于 Lambda 演算法的系列科普視頻

- [程序設(shè)計(jì)方法(中文版)](https://book.douban.com/subject/1140942/):我看的第一版,第二版已經(jīng)出版了,但是我沒(méi)看過(guò)

- [UCB CS61A](https://inst.eecs.berkeley.edu/~cs61a/fa22/):鏈接是 2022 年秋季的課程,這門課程是免費(fèi)的,每年會(huì)同步更新,想看最新的直接搜索 CS61A 即可

- [JavaScript 設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐](https://book.douban.com/subject/26382780/):多范式的 JavaScript 在實(shí)現(xiàn)常用設(shè)計(jì)模式時(shí)并不一定要照抄 Java 的實(shí)現(xiàn),這本書(shū)的常見(jiàn)設(shè)計(jì)模式中很多地方都是使用函數(shù)式的方式實(shí)現(xiàn)的,值得一讀

- [十分鐘速通 Y Combinator](https://coderemixer.com/2018/12/07/y-combinator-in-ten-minutes/):使用 JavaScript 實(shí)現(xiàn) Y 組合子的過(guò)程


瞎話 JavaScript 函數(shù)式:add(3)(4)一點(diǎn)都不酷的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國(guó)家法律
涿鹿县| 大埔区| 沐川县| 宜兰市| 龙游县| 景洪市| 山阳县| 得荣县| 雅安市| 蓬溪县| 奉节县| 荆门市| 长白| 措美县| 晋宁县| 梓潼县| 高邮市| 邹平县| 原平市| 白银市| 镇巴县| 宜都市| 稷山县| 安庆市| 玛纳斯县| 南澳县| 神池县| 安溪县| 横山县| 漳州市| 合作市| 四平市| 顺义区| 年辖:市辖区| 周宁县| 壶关县| 江安县| 怀安县| 怀柔区| 普洱| 松江区|