煙霧的方塊藝術 —MattysFlicks —(CC BY 2.0)
注意:這是從基礎學習函數語言程式設計和使用 JavaScript ES6+ 撰寫軟體的第二部分。保持關注,接下來還有很多!
第一篇 | 第三篇 >
忘掉你認為知道的關於 JavaScript 的一切,用初學者的眼光去看待它。為了幫助你做到這一點,我們將會從頭複習一下 JavaScript 的基礎,就像你與其尚未謀面一樣。如果你是初學者,那你就很幸運了。最終從零開始探索 ES6 和函數語言程式設計!希望所有的概念都被解釋清楚 — 但不要太依賴於此。
如果你是已經熟悉 JavaScript 或者純函式式語言的老開發者了,也許你會認為 JavaScript 是探索函數語言程式設計有趣的選擇。把這些想法放在一邊,用更開放的思想接觸它,你會發現 JavaScript 程式設計更高層次的東西。一些你從來不知道的東西。
由於這個被稱為“組合式軟體”,同時函數語言程式設計是明顯的構建軟體的方法(使用函式組合,高階函式等等),你也許想知道為什麼我不用 Haskell、ClojureScript,或者 Elm,而是 JavaScript。
JavaScript 有函數語言程式設計所需要的最重要的特性:
- 一級公民函式: 使用函式作為資料值的能力:用函式傳參,返回函式,用函式做變數和物件屬性。這個屬性允許更高階別的函式,使偏函式應用、柯里化和組合成為可能。
- 匿名函式和簡潔的 lambda 語法:
x => x * 2
是 JavaScript 中有效的函式表示式。簡潔的 lambda 語法使得高階函式變的簡單。 - 閉包: 閉包是一個有著自己獨立作用域的捆綁函式。閉包在函式被建立時被建立。當一個函式在另一個函式內部被建立,它可以訪問外部函式的變數,即使在外部函式退出後。通過閉包偏函式應用可以獲取內部固定引數。固定的引數時繫結在返回函式的作用域範圍內的引數。在
add2(1)(2)
中,1
是add2(1)
返回的函式中的固定引數。
JavaScript 缺少了什麼
JavaScript 是多正規化語言,意味著它支援多種風格的程式設計。其他被 JavaScript 支援的風格包括過程式(命令式)程式設計(比如 C),把函式看作可以被重複呼叫和組織的子程式指令;物件導向程式設計,物件— 而不是函式— 作為初始構造塊;當然,還有函數語言程式設計。多正規化程式語言的劣性在於命令式和麵向物件往往意味著所有東西都是可變的。
可變性指的是資料結構上的變化。比如:
const foo = {
bar: 'baz'
};
foo.bar = 'qux'; // 改變複製程式碼
物件通常需要可變性以便於被方法更新值,在命令式的語言中,大部分的資料結構可變以便於陣列和物件的高效操作。
下面是一些函式式語言擁有但是 JavaScript 沒有的特性:
- 純粹性: 在一些函式式語言中,純粹性是強制的,有副作用的表示式是不被允許的。
- 不可變性: 一些函式式語言不允許轉變,採用表示式來產生新的資料結構來代替更改一個已存的資料結構,比如說陣列或者物件。這樣看起來可能不夠高效,但是大多數函式式語言在引擎下使用 trie 資料結構,具有結構共享的特點:意味著舊的物件和新的物件是對相同資料的引用。
- 遞迴: 遞迴是函式引用自身來進行迭代的能力。在大多數函式式語言中,遞迴是迭代的唯一方式,它們沒有像
for
、while
、do
這類迴圈語句。
純粹性: 在 JavaScript 中,純粹性由約定來達成,如果你不是使用純函式來構成你的大多數應用,那麼你就不是在進行函式式風格的程式設計。很不幸,在 JavaScript 中,你很容易就會不小心建立和使用一些不純的函式。
不可變性: 在純函式式語言中,不可變性通常是強制的,JavaScript 缺少函式式語言中高效的、基於 trie 樹的資料結構,但是你可以使用一些庫,包括 Immutable.js 和 Mori,由衷期望未來的 ECMAScript 規範版本可以擁抱不可變資料結構。
有一些跡象帶來了希望,比如說在 ES6 中新增了 const
關鍵字,const
宣告的變數不能被重新賦值,重要的是要理解 const
所宣告的值並不是不可改變的。
const
宣告的物件不能被重新宣告為新的物件,但是物件的屬性卻是可變的,JavaScript 有 freeze()
物件的能力,但是這些物件只能在根例項上被凍結,意味著巢狀著的物件還是可以改變它的屬性。換句話說,在 JavaScript 規範中看到真正的不可變還有很長的路要走。
遞迴: JavaScript 技術上支援遞迴,但是大多數函式式語言都有尾部呼叫優化的特性,尾部呼叫優化是一個允許遞迴的函式重用堆疊幀來遞迴呼叫的特性。
沒有尾部呼叫優化,一個呼叫的棧很可能沒有邊界導致堆疊溢位。JavaScript 在 ES6 規範中有一個有限的尾呼叫優化。不幸的是,只有一個主要的瀏覽器引擎支援它,這個優化被部分應用隨後從 Babel(最流行的 JavaScript 編譯器,在舊的瀏覽器中被用來把 ES6 編譯到 ES5) 中移除。
最重要的事實:現在使用遞迴來作為大的迭代還不是很安全 — 即使你很小心的呼叫尾部的函式。
什麼又是 JavaScript 擁有但是純函式式語言缺乏的
一個純粹主義者會告訴你 JavaScript 的可變性是它的重大缺點,這是事實。但是,引起的副作用和改變有時候很有用。事實上,不可能在規避所有副作用的情況下開發有用的現代應用。純函式式語言比如說 Haskell 使用副作用,使用 monads 包將有副作用的函式偽裝成純函式,從而使程式保持純淨,儘管用 Monads 所帶來的副作用是不純淨的。
Monads 的問題是,儘管它的使用很簡單,但是對一個不是很熟悉它的人解釋清楚它有點像“對牛談琴”。
“Monad說白了不過就是自函子範疇上的一個么半群而已,這有什麼難以理解的?” ~James Iry 所引用 Philip Wadler 的話,解釋一個 Saunders Mac Lane 說過的名言。“程式語言簡要、不完整之黑歷史”
典型的,這是在調侃這有趣的一點。在上面的引用中,關於 Monads 的解釋相比最初的有了很大的簡化,原來是下面這樣:
“
X
中的 monad 是其 endofunctor 範疇的么半群,生成 endofunctor 和被 endofunctor 單位 set 組合所代替的X
” ~ Saunders Mac Lane。 "Categories for the Working Mathematician"
儘管這樣,在我的觀點看來,害怕 Monads 是沒有必要的,學習 Monads 最好的方法不是去讀關於它的一堆書和部落格,而是立刻去使用它。對於大部分的函數語言程式設計語言來說,晦澀的學術詞彙比它實際概念難的多,相信我,你不必通過了解 Saunders Mac Lane 來了解函數語言程式設計。
儘管它不是對所有的程式設計風格都絕對完美,JavaScript 無疑是作為適應各種程式設計風格和背景的人的通用程式語言被設計出來的。
根據 Brendan Eric 所言,在一開始的時候,網景公司就有意適應兩類開發者:
“...寫元件的,比如說 C++ 或者 Java;寫指令碼的、業餘的和愛好者,比如直接寫嵌在 HTML 裡的程式碼的。”
本來,網景公司的意向是支援兩種不同的語言,同時指令碼語言大致要像 Scheme (一個 Lisp 的方言),而且,Brendan Eich:
“我被招聘到網景公司,目的是在瀏覽器中 做一些 Scheme”。
JavaScript 應當是一門新的語言:
“上級工程管理的命令是這門語言應當像 Java,這就排除了 Perl,Python,和 Tcl,以及 Scheme。”
所以,Brendan Eich 最初腦子裡的想法是:
- 瀏覽器中的 Scheme。
- 看起來像 Java。
它最終更像是個大雜燴:
“我不驕傲,但我很高興我選擇了 Scheme 的一類函式和 Self(儘管奇怪)的原型作為主要的元素。”由於 Java 的影響,特別是 y2k 的 Date 問題以及物件的區別(比如 string 和 String),就不幸了。”
我列出了這些 “不好的” 的類 Java 特性,最後整理成 JavaScript:
- 建構函式和
new
關鍵子,跟工廠函式有著不同的呼叫和使用語義。 class
的關鍵字和單一父類extends
作為最初的繼承機制。- 使用者更習慣於把
class
看作是它的靜態型別(實際並非如此)。
我的意見:永遠避免使用這些東西。
很幸運 JavaScript 成為了這樣厲害的語言,因為事實上證明指令碼的方式贏了那些建立在“元件”上的方式(現在,Java、Flash、和 ActiveX 擴充套件已經不被大部分安裝的瀏覽器支援)。
我們最終創作了一個直接被瀏覽器支援的語言:JavaScript。
那意味著瀏覽器可以減少臃腫和問題,因為它們現在只需要支援一種語言:JavaScript。你也許認為 WebAssembly 是例外,但是 WebAssembly 設計之初的目的是使用相容的抽象語法樹來共享 JavaScript 的語言繫結(AST)。事實上,最早的把 WebAssembly 編譯成 JavaScript 的子集的示範是 ASM.js。
作為 web 平臺唯一的通用標準程式語言,JavaScript 在軟體歷史潮流中乘風直上:
App 吞食世界, web 吞食 app, 同時 JavaScript 吞食 web。
根據多個平臺調查,JavaScript 是目前世界上最流行的語言。
JavaScript 並不是函數語言程式設計的理想化工具,但是它卻是為大型的分散式的團隊開發大型應用的好工具,因為不同的團隊對於如何構建一個應用或許有不同的看法。
一些團隊致力於指令碼化,那麼命令式的程式設計就特別有用,另外一些更精於抽象架構,那麼一點保留的物件導向方法也許不失為壞。還有一些擁抱函數語言程式設計,使用純函式來確保穩定性、可測試性和專案狀態管理以便減少使用者的反饋。團隊裡的這些人可以使用相同的語言,意味著他們可以更好的交換想法,互相學習和在其他人的基礎上更進一步的開發。
在 JavaScript 中,所有這些想法可以共存,這樣就讓更多的人開始擁抱 JavaScript,然後就產生了世界上最大的開源包管理器 (2017 年 2 月),npm。
JavaScript 的真正優勢在於其生態系統中的思想和使用者的多樣性。它也許不是純函數語言程式設計最理想的語言,但它是你可以想象的工作在不同平臺的人共同合作的理想語言,比如說 Java、Lisp 或者 C。JavaScript 也許並不對有這些背景的使用者完全友好,但是這些人很樂意學習這門語言並迅速投入生產。
我同意 JavaScript 並不是對函數語言程式設計者最好的語言。但是,沒有任何其他語言可以聲稱他們可以被所有人使用,同時正如 ES6 所述:JavaScript 可以滿足到更與喜歡函數語言程式設計的人的需要,同時也越來越好。相比於拋棄 JavaScript 和世界上幾乎每家公司都使用的令人難以置信的生態系統,為什麼不擁抱它,把它變成一個更適合軟體組合化的語言?
現在,JavaScript 已經是一門足夠優秀的函數語言程式設計語言,意味著人們可以使用 JavaScript 的函數語言程式設計方法來構造很多有趣的和有用的東西。Netflix(和其他使用 Angular 2+ 的應用)使用基於 RxJS 的函式式功能。Facebook在 React 中使用純函式、高階函式和高階元件來開發 Facebook 和 Instagram,PayPal、KhanAcademy、和Flipkart使用 Redux 來進行狀態管理。
它們並不孤單:Angular、React、Redux 和 Lodash 是 JavaScript 生態系統中主要的框架和庫,同時它們都被函數語言程式設計很深的影響到— 在 Lodash 和 Redux 中,明確地表達是為了在實際的 JavaScript 應用中使用函數語言程式設計模式。
“為什麼是 JavaScript?”因為 JavaScript 是實際上大多數公司開發真實的軟體所使用的語言。無論你對它是愛是恨,JavaScript 已經取代了 Lisp 這個數十年來 “最受歡迎的函數語言程式設計語言”。事實上,Haskell 更適合當今函數語言程式設計概念的標準,但是人們並不使用它來開發實際應用。
在任何時候,在美國都有近十萬的 JavaScript 工作需求,世界其他地方也有數十萬的量。學習 Haskell 可以幫助你很好的學習函數語言程式設計,但學習 JavaScript 將會教會你在實際工作中開發應用。
App 正在吞食世界, web 正在吞食 app, 同時 JavaScript 正在吞食 web。
下一步
想更多的學習 JavaScript 的函數語言程式設計?
Learn JavaScript with Eric Elliott,什麼,你還不是其中之一,out 了!
Eric Elliott 是 “Programming JavaScript Applications” (O’Reilly) 和 “Learn JavaScript with Eric Elliott” 的作者。他曾效力於 Adobe Systems, Zumba Fitness, he Wall Street Journal, ESPN, BBC, and top recording artists including Usher, Frank Ocean, Metallica 和其他一些公司。
他和她的老婆(很漂亮)大部分時間都在舊金山灣區裡。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃。