我是怎樣走上函數語言程式設計的不歸路的
前幾天的一篇文章引來了一些爭議。文章收到一些反對意見,其中大部分屬於無理取鬧和誤讀,但也有認真探討的。網友 doodlewind 表達了他對於函數語言程式設計的傲慢的不滿。我在這裡回覆一下。
一,這場爭論是怎樣產生的
我在之前的文章也提到過我最初寫《如何在 JS 程式碼中消滅 for 迴圈》(下面簡稱“消滅 for 迴圈”)這篇文章的背景。當時進了新公司,發現程式碼庫裡甚少看到高階函式,迭代幾乎都是用 for 迴圈實現的。為了讓別人看懂這些程式碼,同事們也寫了大量註釋。而我覺得寫程式碼本不該如此費力,如果用點抽象的話,不僅能讓程式碼更容易讀,易寫,甚至都不用寫註釋。如果前同事看到了這篇文章,還望見諒,我純粹從技術層面上提出我的改進想法,沒有任何 diss 的意思。大家共事也很愉快,前同事們也很上進愛學。
我相信像 doodlewind 那麼優秀的開發者,看到這種程式碼,應該不會很愉快。再怎麼反感函式式的霸權,程式碼中適量的過程抽象總該要有吧?當時我總結了我過去一年中學到的相關的知識,做了一次技術分享,後來把這次分享內容發到掘金了,然後才有後來的故事。
以上為背景。下面我講下我那篇文章的一些影響源。
首先是 Joel Thoms 在 Hacker Noon 發表的 Rethink JavaScript 系列文章。我在還沒找到工作時就看完這個系列了。這裡我要承認我的一些不足。我因為不是科班出身,所以對學習到的新知識,無法像 doodlewind 那樣將其放到 CS 整個背景中去驗證和反思。我都是看作者資歷,幾乎全盤接受。Joel Thoms 的自我介紹是 Computer Scientist and Technology Evangelist with 20+ years of experience with JavaScript! 這麼牛逼的 Title,我一個初學者當時還沒辦法去質疑。
然後就是我之前提過的 Dr. Boolean. 他基本上不用 for 迴圈,也經常 diss for 迴圈。再次推薦ofollow,noindex">A Million Ways to Fold in JS
Joel Thoms 的Death of the For Loop 為我寫“消滅 for 迴圈”壯了膽。甚至用遞迴替代迴圈這麼有爭議的內容,我當時也是以一種有巨人站在背後撐腰的心態寫出來了。
二,我為什麼學函數語言程式設計
從我之前分享的學習資源和上面介紹的背景,大家應該能看出來我學程式設計主要是學的來自美國的內容,而美國的前端屆,目前函數語言程式設計確實處於霸權地位。先消除下誤會,我不是說因為是美國的,所以先進。這裡只是介紹背景。在這個背景下,我學習函數語言程式設計是很自然的一件事。事實上,我一開始學程式設計就在有意識地學習函數語言程式設計 。
這裡要介紹兩個女生,大家應該很少有人聽過。
一是 Anjana Vakil,她本科學哲學,研究生學語言學,典型文科生。現在她是一家叫 Mapbox 公司的 Engineering Learning and Development Lead。2016 年年底,我已近決定開始轉行學程式設計了,當時 JS 還只看了一點點,沒入門。偶然一次機會在 YouTube 看到 Anjana 的演講,主題是Learning Functional Programming with JavaScript 。
演講開場白是這樣的(這裡我就不秀英文了,直接翻譯了):“大家好,我是 Anjana, 我第一份工作是英文老師,後來去做了計算語言學家,然後我又來做軟體開發了。就在 6 個月前,我根本不懂什麼是函數語言程式設計,也不會 JS。那麼現在呢,我來給大家分享下怎麼用 JS 做函數語言程式設計。”當時很受她的故事鼓舞,所以這個演講看了好幾遍。所以,我在 JS 還沒入門,還不懂 this 指標和高階函式的時候,就已經知道別人說的函數語言程式設計的好處了。我也是在這個時候聽說了Ramda
很強大,然後記下來了以後學。
二是 Preethi Kasireddy,她的故事也很傳奇。她是學商科的,後來進了 Andreessen Horowitz 這種頂級投行工作。別人搶破頭的金飯碗,她覺得沒意思,辭職後從零開始學程式設計,從事軟體開發了。她一開始是在 freeCodeCamp 學習,在社群裡面比較活躍。剛好我也是在 2017 年初瞭解到 FCC。她很聰明,一開始就有意識採用費曼學習法,用教的方式來學。她的方法是在網上做一個 public commitment,告訴社群的人,我要開始給你們教 JS 函數語言程式設計了,報名參加吧。這樣子她就有動力去準備材料然後教給別人了。然後我就報名參加了。現在她那篇宣佈這項活動的文章還在:Learn the fundamentals of functional programming — for free, in your inbox 那個時候我的 JS 算勉強入門,知道一些 API,但是幾乎沒練習過程式碼。所以,學了 Preethi 的教程,也只是留下了印象。
這兩個故事我本打算加進之前寫的學習經驗分享帖裡面的,但那篇文章已經足夠長了,所以沒加。
後來就是 2017 年 5 月份,Eric Elliott 開始連載 Composing Software 系列文章,然後我就一路學過來了。
從上面的經歷可以看出,我幾乎沒有面向物件程式設計的經歷。當然,為了應付面試,我還是去補了一下多型,封裝,和繼承這些面向物件程式設計知識,儘管我對這些概念很無感。
所以,我對面向物件的認知,幾乎全是二手觀點。而對函數語言程式設計的認知,則是自己在程式設計練習中豐富起來的。
三,堅持函數語言程式設計
其實我也不用以一種捍衛信念的姿態來回應 doodlewind,因為他也沒全盤否定 FP。他的觀點我也不想一一回應,因為我覺得好累,我要把我過去學到的東西再總結下然後去迴應對方,我好想直接扔連結……
其實這些爭論是源自函數語言程式設計和麵向物件程式設計之間幾乎水火不容的對立造成的。我更親近函數語言程式設計,所以堅持的一些觀點,就顯得排斥過程式和指令式的程式碼。很遺憾的是我目前認知有限,不知道怎麼調和這種矛盾。
我還是想從一個初學者的角度,講講為什麼函數語言程式設計值得堅持。我只想講最核心的,就是函數語言程式設計利用程式組合來解決問題的思路,會讓我覺得更容易拆解複雜問題。讓我們暫時擱置“組合優於繼承”這種教條,去看看程式組合是怎麼解決問題的。對於不管多麼複雜的任務,我們都能將其拆解成小任務。而這些小任務,也有可能在其它複雜任務裡面用到。為了儘量複用我們拆出來的這些小任務,我們要保證它沒有副作用。這也是為什麼函數語言程式設計這麼排斥副作用。在我們做了合理拆分之後,再把這些體現為純函式的小任務組合起來,就實現了程式功能。這些簡單描述我在之前的文章中有大量程式碼例子,這裡就不展示了。
而程式組合就需要用到高階函式 compose 或者 composeP(來自Ramda 的 Kleisli Composition)。很多人覺得高階函式難懂,而且覺得用高階函式的人是在炫技。這個真的無解了,Redux 裡面有 compose 的啊……
doodlewind 文章裡面有提到計算機底層的部分。這裡我不慚愧地說下我還不是很瞭解底層,我還在學,過一兩年再來聊這個話題吧。這裡我扔一本書吧,程墨的《深入淺出 RxJS》裡面第一章第 11 頁。打字太累就不復述了。程墨大佬如果有空來回答一下也是極好的。
doodlewind 用來嘲諷函數語言程式設計的“自函子上的么半群”也不成立。這句話不過是用術語精煉概括一個概念,是沒問題的。再說這句話甩鍋給 FP 是不對的,因為它來自範疇論。而範疇論是一種高度抽象的知識,一層一層的抽象,讓你只能用較低階抽象概念去簡短解釋高階抽象概念。我想門外漢如果去聽理論物理學家討論專業知識,是不會為他們用術語解釋術語而不滿的吧。如果想聽懂他們的對話,你也只有先去學這些術語。
最後,Promise 不是 Monad, 見連結No, Promise is not a monad