函數語言程式設計的興衰與當前之崛起

鬍子大哈發表於2019-02-22

本文作者: Eric Elliott

編譯:鬍子大哈

翻譯原文:huziketang.com/blog/posts/…

英文連線:The Rise and Fall and Rise of Functional Programming (Composing Software)

轉載請註明出處,保留原文連結以及作者資訊

本文是“組合式軟體”系列的一篇文章,從頭開始學習函數語言程式設計和使用 JavaScript ES6+ 進行軟體程式設計。請繼續關注,後面還有很多相關內容。

在我 6 歲的時候,我每天花很多時間和我最好的朋友一起打遊戲,他們家裡有很多電腦。對我來講它有種魔幻版不可抗拒的力量。有一天我突然問我朋友:“我們怎樣才能自己做一個遊戲呢?”

他也不知道,所以我們一起去問他的爸爸,叔叔從很高的架子上拿下來一本書:Basic。我也由此開始了我的程式設計之旅。後來大學都有代數這門課程,而我對它已經很熟悉了,因為程式設計裡面代數是基礎,到處都是代數。

組合式軟體的興起

在電腦科學剛剛起步的時候,很多電腦科學的理論都還沒有落地。那時候有兩個偉大的電腦科學家:阿隆佐·丘奇和阿蘭·圖靈。他們創造了兩個不同的,但是具有同等效力的通用計算模型。兩個模型都可以計算任何可以計算的東西(著重強調,“通用”)。

阿隆佐·丘奇發明了 λ 演算, λ 演算是基於函式應用的通用計算模型。阿蘭·圖靈則因圖靈機而廣為人知。圖靈機定義了一個理論上的裝置,它可以控制條帶上的符號。他們合作證明了 λ 演算和圖靈機是功能等價的。

λ 演算全部都是關於函式組合。函式組合在軟體開發中是非常富有表現力和說服力的。本文中,我們會討論函式組合在軟體設計中的重要性。

這裡有三點關於 λ 演算的特殊說明:

  1. 函式通常是匿名的。在 JavaScript 中,const sum = (x, y) => x + y 的右邊是匿名函式,即 (x, y) => x + y
  2. λ 演算中的函式只接受單一輸入,它是一元的。如果你需要傳遞多引數,函式會接受第一個輸入並且返回一個新的函式來接受第二個引數,以此類推。一個 n 元函式 (x, y) => x + y 可以表達為一個一元函式:x => y => x + y。這種 n 元函式到一元函式的轉化叫做柯里化。
  3. 函式是一級的。意思是說一個函式可以作為另一個函式的輸入,並且一個函式可以返回另一個函式。

這些特徵一起構成了簡單且容易理解的規則,在組合式軟體中使用函式作為主要編碼單元。在 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() 的宣告,fdouble()ginc()x3。函式呼叫 compose2(double)(inc)(3),實際上是三個不同的函式呼叫:

  1. 首先傳遞 double 返回一個新函式 1;
  2. 新函式 1 以 inc 為引數並且返回一個新函式 2;
  3. 新函式 2 以 3 為引數並且計算 f(g(x)),即 double(inc(3))
  4. x=3 傳遞給 inc()
  5. inc(3) 計算結果是 4;
  6. double(4) 計算結果是 8
  7. 最終返回結果是 8

組合式軟體的過程可以用函式組合圖來表達,看下面程式碼:

    append = s1 => s2 => s1 + s2
    append(`Hello, `)(`world!`)複製程式碼

可以用圖來模擬表示:

函數語言程式設計的興衰與當前之崛起

λ 演算對軟體設計的影響是深遠的,直到大約 1980 年,電腦科學界很多有影響力的品牌,都是採用函式組合的方式來開發自己的軟體。Lisp 是 1958 年發明的,它深受 λ 演算的影響。直到今天,Lisp 是依舊廣為使用的第二大歷史悠久的語言。

我是通過 AutoLISP 知道的 Lisp,AutoLISP 是在最流行的電腦輔助設計(CAD)軟體——AutoCAD,中使用的指令碼語言。AutoCAD 太流行了,使得其他所有的 CAD 應用幾乎都支援 AutoLISP 以保持其相容性。Lisp 依然能夠在電腦科學課程中廣為使用有三個主要原因:

  1. Lisp 非常簡單,基本上可以在一天之內學習完它的基本語法和語義;
  2. Lisp 基本上全部是函式組合,函式組合來做應用架構的方式非常優雅;
  3. 我所知道的最好的電腦科學課本使用Lisp:計算機程式的結構和解釋

組合式軟體的沒落

在 1970 到 1980 年期間,軟體開發的方式開始發生變化,簡單的組合式開發不再受寵。出現了物件導向程式設計,它基於元件封裝和資訊傳遞的思想,在當時是非常先進的。程式碼通過繼承來實現複用,繼承關係是一種叫做 is-a 的關係。

函數語言程式設計逐漸被邊緣化,被拋棄到學術界和非主流的場外。在 1990 — 2010 年期間對三種人形成了甜蜜的困擾,一種是程式設計極客,一種是大學教授,一種是逃離了 Java 思想強制灌輸的幸運的學生。而對於我們來講,這 30 年的軟體開發有一點噩夢般的感覺,黑暗的年代。

組合式程式設計的重新崛起

2010 年左右,有個巨大的變化:JavaScript 爆發了。在 2006 年以前,JavaScript 一直被認為是一種玩具式的程式語言,可以在瀏覽器中做一些很可愛的動畫,但是在這背後隱藏著潛力巨大的特點,即 λ 演算的重要特徵。人們開始逐漸在私下裡談論“函數語言程式設計”。

到 2015 年,用函式組合來開發軟體重新開始流行起來。為了簡化使用,JavaScript 規範也做了 10 年以來的首次重大升級,增加了箭頭函式。箭頭函式使建立和使用函式、柯里化和 λ 演算變得很容易。

箭頭函式對於 JavaScript 函數語言程式設計的爆發起到了推動劑的作用。現在很少看到那種不用函數語言程式設計的大型應用了。

組合的方式可以簡潔清晰地描述軟體的行為,把一些小的、確定性的函式組合成大的元件,進而形成軟體,這樣的軟體很容易組織、理解、除錯、擴充套件、測試和維護。

在讀接下來文章的時候,希望你能通過例子自己動手做實驗。回想一下當自己還是的孩子的時候,把一些東西拆開,自己再學著組裝、拼接。重新找回童年探索事物的感覺,希望你能享受這個過程。

如果本文對你有幫助,歡迎關注我的專欄-前端大哈,定期釋出高質量前端文章。


我最近正在寫一本《React.js 小書》,對 React.js 感興趣的童鞋,歡迎指點

相關文章