理解函數式編程語言中的組合--前言(一)

理解函數式編程語言中的組合--前言(一)

函數式編程思想可以用一句話總結,即:可組合的類型+可組合的函數,我在《使用函數式語言做領域建模》一文描述瞭如何使用可組合的類型進行領域建模。這篇文章就是用來說明後半部分,即--理解可組合的函數。我假設讀者已經對“Higher order function”, “Currying“, ”Immutable“等基本概念有所瞭解,並擁有基礎的TypeScript知識即可。
這一切還得從抽象說起。

抽象的重要性

人類能夠解決複雜事物的一個重要方法就是抽象,代碼也一樣。有了抽象,你的代碼纔不會變成流水賬,實際上那些讓人讀起來賞心悅目的好代碼,一定擁有良好的抽象。抽象可以讓你避免陷入細節,通過幾個重要的類名或者接口,讓別人快速識別你的設計。
當然,這一切的前提是,閱讀代碼的人也擁有類似的抽象思想,他才能夠心領神會你的意圖。如果你在讀代碼的時候,自己是怎麼想的,正好代碼就是這樣設計的,你纔會覺得代碼可讀性太強了。
怎麼樣大家才能設計出一致的抽象呢?
《設計模式》就是這樣一本把常見問題總結成一些列模式的書籍。換句話說,《設計模式》提供了常見問題的抽象方式並提供了相關的術語。
當然你的設計好不好,還取決於你是否正確識別到了問題的本質,並恰如其分的實現了某個已知的設計模式。如果閱讀代碼的人擁有跟你類似的抽象知識,他就能夠迅速明白你的設計和意圖。

函數式編程中的抽象

如果說《設計模式》總結了OO思想中的常見抽象方式,那麼函數式編程語言也擁有自己的抽象方式。
看下面的這兩行數學運算:

1 + 2 = 3

這行數學運算非常簡單,以至於靠直覺就能判斷他的真確性。這行代碼可以描述爲:兩個“數字”通過“相加”任然爲“數字”。
如果我們把"數字“泛化,將"相加”推廣開來,就可以用在其他事物身上,例如:

"a" + "b" = "ab"

對字符串也是適用的,兩個字符串通過一個運算符合併爲一個新的字符串。沒錯,這就是組合的基礎。
函數式編程中的抽象叫做《範疇輪》Category theory,是一門研究如何組合事物的數學科學。其中,“事物”被稱爲object, 但這個object並不是OO中的那個對象,而事物之間的轉化或者映射被稱爲morphisms,翻譯爲中文叫射態,對應到編程語言中,就是函數。《範疇論》在函數式編程語言中的地位,可以類比《設計模式》在OO中的地位。

爲什麼需要《範疇論》

這要從函數的組合開始說起,我們知道函數式編程語言沒有OO語言中的那些概念,例如類,繼承,依賴注入(有的語言有FP和OO兩種範式, 例如Scala, F#等,請不要糾結)。只靠函數,爲大型的工程化實踐帶來了挑戰。 玩過樂高的同學都知道,樂高的零件都是擁有獨立功能的小部件,然而,卻有人用它拼出了汽車,飛機甚至是航空母艦,這充分說明了組合的力量。
那麼函數是如何組合的呢?看下面的代碼:

const add = (a: number, b: number) => a + b
const sub1 = (a: number) => a - 1 

聲明兩個函數add和sub1, 像lodash或fp-ts等庫都會提供一個叫flow的函數,flow的作用就是把若干個函數組合起來:

const addThenSub1 = flow(add, sub1)

expect(addThenSub1(1, 2)).toBe(2)

此時addThenSub1變成了一個新的函數,最後再將參數作用在上面就可以得到結果。flow理論上可以支持任何數量的函數組合。然而,flow有個致命的問題,他要求上一個函數的返回值類型跟下一個函數的輸入類型一致,例如,上面例子中的add如果返回string, 那麼連接就失敗了。另外對sub1的參數數量也有要求,即除了第一個函數add, 後面的函數只能接受一個參數。
其實這兩條限制就爲函數的組合判了死刑,即:
雖然函數是可以組合的,但並不是所有的函數都能任意組合。
《範疇論》就是這樣一門數學科學,他幫你抽象出了事物的轉化或組合模式,你只要按照他總結的模式設計,就能將函數組合起來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章