終於到了 FP 的第四篇了,本篇是 FP 的最後一篇,要來談一些 FP 的進階議題:
函數式編程慣用法主要有 mapcat 與 if patterns。
在 Clojure 中,mapcat
是 map
和 concat
的結合,它的作用是將一個函數應用到集合中的每個元素,然後將所有返回的序列拼接成一個單一的序列。這在我們需要從集合中的每個元素產生一個新的集合,並將這些小集合合併成一個大集合時特別有用。
舉個例子來說,假設我們有一個包含多個子集合的集合,我們想將它們扁平化(flatten)成一個單一的序列。
(def data '((1 2) (3 4) (5 6)))
;; 使用 map 和 concat
(apply concat (map identity data))
;; => (1 2 3 4 5 6)
;; 使用 mapcat
(mapcat identity data)
;; => (1 2 3 4 5 6)
另一個更常見的應用場景是,我們需要處理一個集合,並從每個元素中產生一個包含多個值的新序列。
(def users '("Alice" "Bob" "Charlie"))
;; 假設我們有一個函數,可以從使用者名稱產生與其相關的社交媒體帳號
(defn get-social-media-accounts [user]
(condp = user
"Alice" '("twitter/alice" "github/alice")
"Bob" '("twitter/bob")
"Charlie" '("twitter/charlie" "linkedin/charlie")))
;; 使用 mapcat 來取得所有使用者的所有社群帳號,並將其合併為一個序列
(mapcat get-social-media-accounts users)
;; => ("twitter/alice" "github/alice" "twitter/bob" "twitter/charlie" "linkedin/charlie")
if patterns 算是一類相當變化相當多的慣用法,篇幅較長。有興趣的讀者可以參考這篇。
由於物件導向編程 (OOP) 比 FP 流行得多,大家在談論 OOP 的優點時,時不時也會談到,OOP 可以支援軟體復用 (code reuse) ,因為 OOP 提供了 Interface 等的鬆耦合機制 (polymorphisms)。
那 FP 呢?FP 也支援程式碼復用嗎?
在談論程式碼復用之前,不妨先多討論一下鬆耦合機制。
最極致的鬆耦合機制,應該是傳輸協定 (Protocol) ,因為即使要對接的雙方連 Runtime 都不同,只要透過網路,且彼此可以互相傳輸資料,依照傳輸協定來互相溝通,不同的系統就可以介接起來。
鬆耦合機制如果是在物件導向程式語言,包含 Java, Lua ,則常會透過 Java 的 interface 或 Lua 的 metatable 來實作,它常常會是一種 Service Provider Interface。日後,如果要替換鬆耦合所接合的舊模組,我們只要開發新的模組且該新模組有實作相同的 Java interface 或是註冊一樣的 Lua metatable 函式,新模組就可以順利替換舊模組。
也因此,我們可以說,物件導向程式語言提供的鬆耦合機制是一種:「函數簽名做為傳輸協定」。
那 FP 呢?在典型的 FP 的程式裡,我們並不會產生很多物件,因為沒有必要。要對資料建模 (modeling) 的話,多數時候,我們使用串列或是字典這些資料結構 (data structure) 即可,所以程式碼庫裡會有的就是大量的資料結構與函數。
那有鬆耦合機制嗎?還是有的。
首先,資料結構本身就是一種極佳的鬆耦合機制。回想一下,在之前傳輸協定的例子裡,傳輸協定就是規定溝通的雙方,彼此傳輸正確形狀的資料,如此而已。換言之,我們可以說,函數式程式語言提供的鬆耦合機制是一種:「資料結構做為傳輸協定」。
此外,還有一個很重要的概念,其實許多的動態型別語言都有 Duck Typing 的設計。Duck Typing 本身也可以視為是一種「函數簽名做為傳輸協定」的實現。
本篇談到了函數式編程慣用法與程式碼復用。
慣用法的部分,mapcat 真的很常用;if patterns 的話,如果你用了,別人看不懂,不要太苛責別人。
程式碼復用的部分,我們從鬆耦合機制加以分析,討論了「傳輸協定」、「函數簽名做為傳輸協定」、「資料結構做為傳輸協定」三種鬆耦合機制。顯然,FP 非常容易達成程式碼復用。