所以函數式編程是什麼(ㄧ)

閱讀時間約 4 分鐘

前面談了那麽多函數式編程與物件導向的差異,但我們還沒定義函數式編程。就像物件導向,函數式編程沒有明確的定義,每個人對於什麼是函數式編程都有不同的看法。在這裡我會總結前面的討論,給出我對於函數式編程的觀點。


物件導向注重封裝與延展性,因此一般基於三個機制:繼承、多型、封裝,它們代表了物件導向所重視的部分。而函數式編程著重於規則,認為規則的定義應該要明確且嚴格,因此它特別重視實現規則的機制。我認為的函數式編程則基於三個機制:類型、泛型、多型。


「類型」的機制指的是每個變數應該要有唯一的類型,它用來描述值的資料結構。它同時可以用來描述資料的意義,因此不是結構一樣就是相同的類型,也就是所謂的nominal type。函數式編程強調變數類型的明確性,因此不使用像是類別的抽象類型描述資料,而是透過多型描述抽象概念。相對地,為了讓類型具有延展性與可重用性,函數式編程使用複合類型組合多個類型,而非透過繼承延伸已有的類型。對於函數式編程,函式本身也有類型,而它只是其中一種複合類型而已,因此使用函式作為值使用是很正常的。函式的類型描述的是函式的行為,更廣義來說,函式的非同步標示符(async)、可拋出例外標示符(throws)等都可以算作函式類型的一部分。在Haskell則是使用Monad描述這些結構。總結來說,類型的職責是描述資料結構乃至程式結構,而非透明的特性能讓我們根據架構設計賦予這些結構唯一的語義。


「泛型」的機制透過參數化類型實現變數類型的抽象化。不同於物件導向的子類關係,它能保證類型之間的同調性,而這種同調性本身可以用來描述整體的結構。參數化的類型讓我們無法知道變數的具體類型,這使得我們必須以獨立於類型的方式處理它,除非程式語言本身允許實體類型檢查。這種必須與具體類型無關的限制形成對類型的抽象化,泛型本身並不能直接描述有意義的抽象類型,通常會搭配約束和多型的機制使用。如果程式語言不允許子類關係,那麽泛型就是唯一能使用非特定類型的方法,否則所有變數都只能有特定且具體的類型。對類型進行參數化代表把類型本身當作一種數值,並使用一個佔位符取代。更廣義的,不只有類型可以參數化,複合類型的建構子也能參數化,而它具有和類型不同的種類(kind),這種將類型作為值看待的方法使我們能更進一步地思考程式的抽象結構。總結前面的討論,泛型能將具體的結構從程式中抽離出來,變成可替換的抽象結構,這種特定的抽象就是這個機制的職責。


函數式編程的「多型」與物件導向的不同,它指的是特設多型,透過讓一個函式針對不同特定的類型做出不同的行為,讓我們可以描述抽象概念。特設多型能讓程式在不同地方對不同類型實作同一個概念,這為抽象概念提供擴展的能力。這種可擴展的語義讓我們無法直接透過具體類型決定函式的行為,因而形成抽象的概念。函數式編程的特設多型可以藉由對類型參數的約束實現,其中約束通常是使用介面/特性系統描述。對於不允許繼承和實體類型檢查的程式語言,這是唯一能讓同一個函式對不同類型具有不同行為的方法,而這與流程控制的分支不同,它的實際行為可以藉由實體化的類型決定,而不是在執行時期才決定。因此,多型的職責是為相同概念實現不同的行為,並讓我們在程式中實現與描述抽象概念。


函數式編程的這些機制與物件導向很像,但函數式編程的機制是根據能力和規則設計的,而非像是物件導向根據某種特定的面向設計,因此對於問題的解決方案比較不容易出現多種可能的實現方法。例如類型只能描述具體資料結構,不能描述抽象概念;泛型可以用來抽象化資料結構,但不會抹去類型資訊;多型必須藉由泛型與約束完成,它是唯一能實現擴展抽象概念的機制。而物件導向的一些機制對於函數式編程是多餘的,甚至是有害的。例如子類關係讓我們難以判斷變數是否是抽象的;變數的實體類型檢查讓泛型失去行為的一致性。相對的,函數式編程的機制之間的能力是沒有重合的,因此能將各自的能力發揮到極致。這種設計讓變數、函式、介面等編程實體僅具有最少的能力,使得我們能更準確掌握各個部分的程式碼能做什麼和該做什麼。這種最小化能力、最大化用處的設計就是我認為的函數式編程最重要的原則,而這三個機制從語言級別上遵從與支援這個原則。

3會員
23內容數
這不是教你如何從物件導向到函數式編程的入門教程。我會深入探討物件導向與函數式編程的差異,並討論為什麼你應該使用函數式編程並徹底放棄物件導向。
留言0
查看全部
發表第一個留言支持創作者!