本文作者: Eric Elliott
編譯:鬍子大哈翻譯原文:huziketang.com/blog/posts/…
英文連線:The Rise and Fall and Rise of Functional Programming (Composing Software)
轉載請註明出處,保留原文連結以及作者資訊
本文是“組合式軟體”系列的一篇文章,從頭開始學習函數語言程式設計和使用 JavaScript ES6+ 進行軟體程式設計。請繼續關注,後面還有很多相關內容。
在我 6 歲的時候,我每天花很多時間和我最好的朋友一起打遊戲,他們家裡有很多電腦。對我來講它有種魔幻版不可抗拒的力量。有一天我突然問我朋友:“我們怎樣才能自己做一個遊戲呢?”
他也不知道,所以我們一起去問他的爸爸,叔叔從很高的架子上拿下來一本書:Basic。我也由此開始了我的程式設計之旅。後來大學都有代數這門課程,而我對它已經很熟悉了,因為程式設計裡面代數是基礎,到處都是代數。
組合式軟體的興起
在電腦科學剛剛起步的時候,很多電腦科學的理論都還沒有落地。那時候有兩個偉大的電腦科學家:阿隆佐·丘奇和阿蘭·圖靈。他們創造了兩個不同的,但是具有同等效力的通用計算模型。兩個模型都可以計算任何可以計算的東西(著重強調,“通用”)。
阿隆佐·丘奇發明了 λ 演算, λ 演算是基於函式應用的通用計算模型。阿蘭·圖靈則因圖靈機而廣為人知。圖靈機定義了一個理論上的裝置,它可以控制條帶上的符號。他們合作證明了 λ 演算和圖靈機是功能等價的。
λ 演算全部都是關於函式組合。函式組合在軟體開發中是非常富有表現力和說服力的。本文中,我們會討論函式組合在軟體設計中的重要性。
這裡有三點關於 λ 演算的特殊說明:
- 函式通常是匿名的。在 JavaScript 中,
const sum = (x, y) => x + y
的右邊是匿名函式,即(x, y) => x + y
。 - λ 演算中的函式只接受單一輸入,它是一元的。如果你需要傳遞多引數,函式會接受第一個輸入並且返回一個新的函式來接受第二個引數,以此類推。一個 n 元函式
(x, y) => x + y
可以表達為一個一元函式:x => y => x + y
。這種 n 元函式到一元函式的轉化叫做柯里化。 - 函式是一級的。意思是說一個函式可以作為另一個函式的輸入,並且一個函式可以返回另一個函式。
這些特徵一起構成了簡單且容易理解的規則,在組合式軟體中使用函式作為主要編碼單元。在 JavaScript 中,匿名函式和柯里化函式都是可選特徵,也就是說 JavaScript 支援 λ 演算的主要特徵但是並不強制使用。
經典的函式組合是把一個函式的輸出作為另一個函式的輸入,例如下面的組合:
f·g複製程式碼
可以寫成:
compose2 = f => g => x => f(g(x))複製程式碼
下面是如何使用它:
double = n => n * 2
inc = n => n + 1
compose2(double)(inc)(3)複製程式碼
compose2()
函式接受 double
函式作為第一個引數,inc
函式作為第二個引數,最後應用引數 3 到這兩個函式組合上。再看一下 compose2()
的宣告,f
是 double()
,g
是 inc()
,x
是 3
。函式呼叫 compose2(double)(inc)(3)
,實際上是三個不同的函式呼叫:
- 首先傳遞
double
返回一個新函式 1; - 新函式 1 以
inc
為引數並且返回一個新函式 2; - 新函式 2 以
3
為引數並且計算f(g(x))
,即double(inc(3))
; x=3
傳遞給inc()
;inc(3)
計算結果是4
;double(4)
計算結果是8
;- 最終返回結果是
8
。
組合式軟體的過程可以用函式組合圖來表達,看下面程式碼:
append = s1 => s2 => s1 + s2
append('Hello, ')('world!')複製程式碼
可以用圖來模擬表示:
λ 演算對軟體設計的影響是深遠的,直到大約 1980 年,電腦科學界很多有影響力的品牌,都是採用函式組合的方式來開發自己的軟體。Lisp 是 1958 年發明的,它深受 λ 演算的影響。直到今天,Lisp 是依舊廣為使用的第二大歷史悠久的語言。
我是通過 AutoLISP 知道的 Lisp,AutoLISP 是在最流行的電腦輔助設計(CAD)軟體——AutoCAD,中使用的指令碼語言。AutoCAD 太流行了,使得其他所有的 CAD 應用幾乎都支援 AutoLISP 以保持其相容性。Lisp 依然能夠在電腦科學課程中廣為使用有三個主要原因:
- Lisp 非常簡單,基本上可以在一天之內學習完它的基本語法和語義;
- Lisp 基本上全部是函式組合,函式組合來做應用架構的方式非常優雅;
- 我所知道的最好的電腦科學課本使用Lisp:計算機程式的結構和解釋
組合式軟體的沒落
在 1970 到 1980 年期間,軟體開發的方式開始發生變化,簡單的組合式開發不再受寵。出現了物件導向程式設計,它基於元件封裝和資訊傳遞的思想,在當時是非常先進的。程式碼通過繼承來實現複用,繼承關係是一種叫做 is-a
的關係。
函數語言程式設計逐漸被邊緣化,被拋棄到學術界和非主流的場外。在 1990 — 2010 年期間對三種人形成了甜蜜的困擾,一種是程式設計極客,一種是大學教授,一種是逃離了 Java 思想強制灌輸的幸運的學生。而對於我們來講,這 30 年的軟體開發有一點噩夢般的感覺,黑暗的年代。
組合式程式設計的重新崛起
2010 年左右,有個巨大的變化:JavaScript 爆發了。在 2006 年以前,JavaScript 一直被認為是一種玩具式的程式語言,可以在瀏覽器中做一些很可愛的動畫,但是在這背後隱藏著潛力巨大的特點,即 λ 演算的重要特徵。人們開始逐漸在私下裡談論“函數語言程式設計”。
到 2015 年,用函式組合來開發軟體重新開始流行起來。為了簡化使用,JavaScript 規範也做了 10 年以來的首次重大升級,增加了箭頭函式。箭頭函式使建立和使用函式、柯里化和 λ 演算變得很容易。
箭頭函式對於 JavaScript 函數語言程式設計的爆發起到了推動劑的作用。現在很少看到那種不用函數語言程式設計的大型應用了。
組合的方式可以簡潔清晰地描述軟體的行為,把一些小的、確定性的函式組合成大的元件,進而形成軟體,這樣的軟體很容易組織、理解、除錯、擴充套件、測試和維護。
在讀接下來文章的時候,希望你能通過例子自己動手做實驗。回想一下當自己還是的孩子的時候,把一些東西拆開,自己再學著組裝、拼接。重新找回童年探索事物的感覺,希望你能享受這個過程。
如果本文對你有幫助,歡迎關注我的專欄-前端大哈,定期釋出高質量前端文章。
我最近正在寫一本《React.js 小書》,對 React.js 感興趣的童鞋,歡迎指點。