所以Monad到底是什麼 — typeclass

閱讀時間約 6 分鐘

如果你曾經試圖學習函數式編程,並嘗試理解Monad,但看到文件上的定義卻一個字都看不懂,使用的術語、概念和一般常見的語言又很不一樣。網路上的教程往往都是以最簡單的範例試圖解釋Monad,但看到實際案例後又發現你完全不懂。事實上大部分教程的描述並不適用於「所有」的Monad,甚至在某方面來說是錯的,就算有正確的描述,從軟體開發的角度仍然難以理解。我所謂的「軟體開發的角度」是指:當我們處理資料時,會試圖找實際案例理解資料的意義,這麼做能處理哪些狀況,遇到這個例外時又要怎麼辦。這種由下而上、窮舉式的思考是最實際解決問題的方法,然而Monad官方說明文件卻從不告訴你他是什麼,也從不說明遇到某些情況時為什麼仍沒有問題,這讓工程師無所適從,至少我曾經是如此。這個系列的文章目的是希望能從軟體開發的角度理解Monad等typeclass的概念,並試圖把所有我能想到的、遇到的、跟找得到的各種範例與反例一一展開討論,透過它們循序漸進地打破與重建我們對於Monad的印象。其中將以PureScript編寫範例,簡單來說它就是拿掉惰性求值的比較好的 haskell,其程式碼可以轉譯成有意義的js。

Monad 是函數式編程中很重要的概念,很多我們習以為常的指令式操作在純函數式程式語言中必須使用Monad實現,這使得純函數式編程的使用體驗與其他程式語言很不一樣。這並不是因為純函數式程式語言設計的很爛(至少這方面不是如此),而是它把我們之前沒有注意到習以為常的特性明確地展示出來(unknown known);複雜的不是Monad,而是被我們忽略掉的指令式編程的基本結構。因此Monad是進階函數式編程中最重要的概念,也是讓我們重新認識指令式編程的絕佳機會。要了解Monad,就要先談談函數式編程與眾不同的多型機制:typeclass。typeclass是haskell/PureScript實現根據類型而產生不同行為的唯一機制,這個能力類似於物件導向的子類機制,它們常常被用來實現抽象化的行為。事實上並不是只有子類關係和typeclass能夠描述抽象化,程式裡面處處是不同程度的抽象化。

當我們把一段重複出現的程式碼抽離出來共用,並把其中不一樣的地方改成參數,這個過程抹除了參數的實際數值,因此函式是計算過程對於這些變數的抽象化。

當我們處理某些業務邏輯的計算問題時,需要使用一些區域變數暫存一些業務資料,而不論這個資料代表什麼,通常會直接使用已有的基礎結構如List a, Map k v,這些結構本身並不帶有如業務邏輯的實際意義,而是根據使用方式決定意義,因此可以說這些通用容器類是資料對於意義的抽象化。

有時我們並不關心類型的實際結構(例如各種資料庫、各種容器類)而只在意行為(一系列的關聯方法),並希望不同結構但擁有相同行為的物件能夠在相同情況下被使用,而不需要重複編寫幾乎一樣的程式碼,這可以透過介面、typeclass實現,這種介面是物件行為對於實際類型的抽象化。

一些不同的抽象行為也有共通性,例如擁有狀態的操作可以抽象化成定義存取狀態方法的介面,而能夠執行平行運算的操作也可以抽象化成定義派發平行運算任務方法的介面,這兩種介面擁有共通性質:擁有可嵌套的情境,稱為Monad。執行一系列改變狀態的操作可看作一個普通的改變狀態的操作,而在平行執行緒中派發平行運算的任務理想情況下可看作普通地執行平行運算的操作。Monad只在意能否嵌套,實際上在做什麼並不是重點,只要符合特定規則就是一種Monad,這屬於規則對於實際能力的抽象化。

Functor, Applicative, Monad等都是最後一種類型的抽象化,它與我們一般使用介面的方法很不一樣,它描述的是規則而非業務邏輯的抽象化。在類型驅動編程中,我們會使用類型或介面描述特定概念,例如定義Point3D為帶有三個浮點數的結構以代表三維空間中的點,就算已經有一個類型Vector3D也應該這麼做,因為它們是不同的概念。即使我們對這個概念擁有什麼方法,遵守哪些規則都不清楚,也應該這麼做。例如rust的Pin就是如此,它只告訴你要自己定義各個成員的映射方法,要不然根本沒辦法用,真正重要的是它所代表的概念。而Functor、Monad等則恰恰相反,它們只關心規則,只要符合它所定義的規則,就是合法的實作。事實上它們的目的本來就不是抽象化某個概念,而是用來限定類型必須符合某種規則,因此不應該使用原本對介面的理解方式來認識它們。

你可能會想functor, monad, comonad等typeclass所定義的函式與規則實際上並不能做什麼,因為它們只描述了最最基本的特性,而更進一步的特性才能賦予我們操作的能力。我們應該把它當作「限制」而非「能力」,因此這句話就變成:它們只做了非常基本的限制,就能適用於非常多的地方。這點顯示了type class/trait與我們一般認知的interface/ability之間觀點的差異。函數式編程往往只透過限制基本規則來定義通用函式,而非把他當作某種抽象概念的延伸。因此如果要使用某個函式,只要看它符合哪些規則,如果你的情境符合這個規則,就可以根據要求實作相應的typeclass並使用它,而不需要擔心它所代表的概念是否適用於此。這種typeclass強調的是規則、結構,並沒有特別描述什麼概念,而軟體開發所使用interface的方法則更強調抽象概念。如果你從haskell跳到rust就必須注意這一點,haskell以規則為核心,而rust雖然有類似的類型系統,但是那些都是為概念服務的,因此會出現很多複雜的規則,只為了讓它心目中的概念可以運作。

即使這類的typeclass在定義上把實作時該符合哪些規則都寫得清清楚楚,單從規則很難想像他到底是什麼,很容易就會被已知的情報所困住,因而陷入既定印象。例如一般都會把functor描述成容器,因為List, Maybe, Map k等容器都會實作functor,然而不是容器的Function a也實作了functor,因此這並不是合適的描述*。如果沒有人告訴你這些例子,一般是很難自己意識到這種偏見,因此認識這類的typeclass需要更多實例幫助理解,尤其是反例,藉由反問哪些不符合這個規則或是印象,可以更加理解他所代表的概念的邊界。

    4會員
    28內容數
    這不是教你如何從物件導向到函數式編程的入門教程。我會深入探討物件導向與函數式編程的差異,並討論為什麼你應該使用函數式編程並徹底放棄物件導向。
    留言0
    查看全部
    發表第一個留言支持創作者!
    have bear的沙龍 的其他內容
    對於程序式編程來說,程式是由一系列的指令組成,例如計算數值、印出訊息、修改變數、呼叫子程序、配置變數的記憶體空間等。定義函式是為了讓一些程序可以重複利用,因此稱為子程序,其中參數為子程序中特別的變數,讓我們能夠透過它們控制子程序的行為。函式的回傳值只是一種方便將結果帶回來的方法,但一般只能回傳一個值
    函數式編程跟物件導向一個很大的差異在於對資料可變性(mutability)的態度,函數式編程不鼓勵修改原有的資料,有些語言甚至沒有修改的概念;而物件導向專注於狀態的改變,物件作為閉包就已經假設資料是可變的。這種對於可變性的態度注定物件導向比較容易得到關注,因為這個模型比較符合電腦底層的運作邏輯,而我
    這個系列的文章主要專注於物件導向到函數式編程的差異與分析,並針對概念與機制上的不同進行比較。很多人說物件導向和函數式編程沒有哪個比較好的問題,只有哪個比較適合的問題,然而我並不這麼認為,我透過這一系列的文章從各個角度討論它們之間的優缺點就是為了闡述我的觀點。物件導向錯在沒有理論基礎,但它贏在熟悉性,
    前一篇文章中所提的函數式的三個機制明確說明了它關注的規則與能力具體是什麼。然而這套對於函數式編程的定義主要基於特定的類型系統,作為一個編程範式來說過於狹隘(物件導向的定義也是這樣)。更廣義的,我認為函數式編程主要依循三個原則,它們可以應用於任何程式語言,就算沒有靜態類型系統的支援也可以。例如在不管類
    前面談了那麽多函數式編程與物件導向的差異,但我們還沒定義函數式編程。就像物件導向,函數式編程沒有明確的定義,每個人對於什麼是函數式編程都有不同的看法。在這裡我會總結前面的討論,給出我對於函數式編程的觀點。 物件導向注重封裝與延展性,因此一般基於三個機制:繼承、多型、封裝,它們代表了物件導向所重
    說到物件導向就必須提五個原則,統稱SOLID,它被認為是物件導向的重要概念。這五個原則並不只適用於物件導向,事實上它很像函數式編程的習慣。它的命名很奇怪且容易讓人混淆,所以我會用我自己的翻譯解釋。 Single Responsibility Principle 是「單一職責原則」,認為一個模組
    對於程序式編程來說,程式是由一系列的指令組成,例如計算數值、印出訊息、修改變數、呼叫子程序、配置變數的記憶體空間等。定義函式是為了讓一些程序可以重複利用,因此稱為子程序,其中參數為子程序中特別的變數,讓我們能夠透過它們控制子程序的行為。函式的回傳值只是一種方便將結果帶回來的方法,但一般只能回傳一個值
    函數式編程跟物件導向一個很大的差異在於對資料可變性(mutability)的態度,函數式編程不鼓勵修改原有的資料,有些語言甚至沒有修改的概念;而物件導向專注於狀態的改變,物件作為閉包就已經假設資料是可變的。這種對於可變性的態度注定物件導向比較容易得到關注,因為這個模型比較符合電腦底層的運作邏輯,而我
    這個系列的文章主要專注於物件導向到函數式編程的差異與分析,並針對概念與機制上的不同進行比較。很多人說物件導向和函數式編程沒有哪個比較好的問題,只有哪個比較適合的問題,然而我並不這麼認為,我透過這一系列的文章從各個角度討論它們之間的優缺點就是為了闡述我的觀點。物件導向錯在沒有理論基礎,但它贏在熟悉性,
    前一篇文章中所提的函數式的三個機制明確說明了它關注的規則與能力具體是什麼。然而這套對於函數式編程的定義主要基於特定的類型系統,作為一個編程範式來說過於狹隘(物件導向的定義也是這樣)。更廣義的,我認為函數式編程主要依循三個原則,它們可以應用於任何程式語言,就算沒有靜態類型系統的支援也可以。例如在不管類
    前面談了那麽多函數式編程與物件導向的差異,但我們還沒定義函數式編程。就像物件導向,函數式編程沒有明確的定義,每個人對於什麼是函數式編程都有不同的看法。在這裡我會總結前面的討論,給出我對於函數式編程的觀點。 物件導向注重封裝與延展性,因此一般基於三個機制:繼承、多型、封裝,它們代表了物件導向所重
    說到物件導向就必須提五個原則,統稱SOLID,它被認為是物件導向的重要概念。這五個原則並不只適用於物件導向,事實上它很像函數式編程的習慣。它的命名很奇怪且容易讓人混淆,所以我會用我自己的翻譯解釋。 Single Responsibility Principle 是「單一職責原則」,認為一個模組
    你可能也想看
    Google News 追蹤
    Thumbnail
    這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
    Thumbnail
    11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
    Thumbnail
    Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
    不怎麼完美的12月,難忘的不愛的抹茶蛋糕 自由飛翔的藝術和愛情,飛過了布魯克林的大橋,最後,永遠剩下藝術與她相伴,永遠不變的永恆,從小到大其他人是過客如浮雲。 她與他在唱歌痛快斯吼著,舉起手旋轉,說要地老天荒,多少次還是剩下她一個人與心碎。 身邊的男伴如浮雲來去,沒習慣也早已習慣,直到有天和她
    護理位在轉變的世代,而時代進步的路上,新舊觀念的衝突難免,但我們是否具備接受「改變」的勇氣?夾好乾淨的頭髮跟維持儀容就是展現專業形象,那帶著徹夜未眠的疲憊上班,想問疲勞駕駛跟給藥錯誤嚴重度是否相當? 當世代間的隔閡深化,那高聳金字塔腐壞跟停滯也是可預期的,實在難以壯大、難以改變,嘖嘖嘖。
    Thumbnail
    PM要我們分享一個自己去過最寒冷的國家或景點,到目前為止,我只出過兩次國,一次是去Okinawa,另一次是去大陸坐船遊長江三峽,但兩次出國都在夏天,一點也不冷,還熱得要命!所以我去過最寒冷的國家,竟然是——
    Thumbnail
    話說我在〈歲末年終感恩文:感謝一眾帥哥美女的相助與支持!〉裡,曾經深深深深懷疑薯友天老(一個人的天荒地老)有雙重人格,不然怎麼會平常都貼學術論文,可當起優質創作者評審時,又能以生花妙筆為一眾創作者寫出精采絕倫的專屬模板文?
    Thumbnail
    上一次在診間昏倒後,醫生懷疑可能和我的血糖低有關係(因為當時所有數據中只有這項有問題),所以安排我去內分泌科檢查。接著一連串的檢查...
    所以當我們遇到業力現前,懺悔業障,甘心忍受,一心念佛──道晟法師
      所以說,為什麼要長壽呢?   我現在正努力地賺錢,為了可以安心退休。
    Thumbnail
    不過找遍整個網路,談到「槓桿ETF報酬期望值」的人,除了我們之外,就只有PTT DAZE。
    Thumbnail
    採訪高管,不免擔憂多於興奮,憂心問答制式,或是與部門實際工作樣貌有著巨大落差。「來吧請坐!今天我們要聊聊什麼?」正與同仁聊天的文鎧副總抬起頭,像是家中爸爸一般微笑招呼,國泰置地廣場 27 樓的會議室,一瞬間成了溫馨客廳。
    Thumbnail
    關於「自己」這回事,在某個年紀就拋掉那些青少年時讀的那些關於自己的勵志暢銷書後,就沒再積極看市面上任何關於「自己」的任何書籍,但那種四肢無法伸展,總在人群裡不知道手腳怎麼擺的慌亂感,仍然時不時地從心裡蜿蜒出藤蔓布滿全身,讓人無法喘口氣,彷彿無時無刻都在提醒著「你得面對自己」。
    Thumbnail
    這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
    Thumbnail
    11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
    Thumbnail
    Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
    不怎麼完美的12月,難忘的不愛的抹茶蛋糕 自由飛翔的藝術和愛情,飛過了布魯克林的大橋,最後,永遠剩下藝術與她相伴,永遠不變的永恆,從小到大其他人是過客如浮雲。 她與他在唱歌痛快斯吼著,舉起手旋轉,說要地老天荒,多少次還是剩下她一個人與心碎。 身邊的男伴如浮雲來去,沒習慣也早已習慣,直到有天和她
    護理位在轉變的世代,而時代進步的路上,新舊觀念的衝突難免,但我們是否具備接受「改變」的勇氣?夾好乾淨的頭髮跟維持儀容就是展現專業形象,那帶著徹夜未眠的疲憊上班,想問疲勞駕駛跟給藥錯誤嚴重度是否相當? 當世代間的隔閡深化,那高聳金字塔腐壞跟停滯也是可預期的,實在難以壯大、難以改變,嘖嘖嘖。
    Thumbnail
    PM要我們分享一個自己去過最寒冷的國家或景點,到目前為止,我只出過兩次國,一次是去Okinawa,另一次是去大陸坐船遊長江三峽,但兩次出國都在夏天,一點也不冷,還熱得要命!所以我去過最寒冷的國家,竟然是——
    Thumbnail
    話說我在〈歲末年終感恩文:感謝一眾帥哥美女的相助與支持!〉裡,曾經深深深深懷疑薯友天老(一個人的天荒地老)有雙重人格,不然怎麼會平常都貼學術論文,可當起優質創作者評審時,又能以生花妙筆為一眾創作者寫出精采絕倫的專屬模板文?
    Thumbnail
    上一次在診間昏倒後,醫生懷疑可能和我的血糖低有關係(因為當時所有數據中只有這項有問題),所以安排我去內分泌科檢查。接著一連串的檢查...
    所以當我們遇到業力現前,懺悔業障,甘心忍受,一心念佛──道晟法師
      所以說,為什麼要長壽呢?   我現在正努力地賺錢,為了可以安心退休。
    Thumbnail
    不過找遍整個網路,談到「槓桿ETF報酬期望值」的人,除了我們之外,就只有PTT DAZE。
    Thumbnail
    採訪高管,不免擔憂多於興奮,憂心問答制式,或是與部門實際工作樣貌有著巨大落差。「來吧請坐!今天我們要聊聊什麼?」正與同仁聊天的文鎧副總抬起頭,像是家中爸爸一般微笑招呼,國泰置地廣場 27 樓的會議室,一瞬間成了溫馨客廳。
    Thumbnail
    關於「自己」這回事,在某個年紀就拋掉那些青少年時讀的那些關於自己的勵志暢銷書後,就沒再積極看市面上任何關於「自己」的任何書籍,但那種四肢無法伸展,總在人群裡不知道手腳怎麼擺的慌亂感,仍然時不時地從心裡蜿蜒出藤蔓布滿全身,讓人無法喘口氣,彷彿無時無刻都在提醒著「你得面對自己」。