所以Monad 到底是什麼 — Functor

閱讀時間約 8 分鐘

在認識 Monad 之前,必須先了解什麼是 Functor。Functor 定義為可以 map 的類型:

class Functor f where
map :: forall a b. (a -> b) -> (f a -> f b)

這個 map 就是陣列的 map 的推廣,例如 js 的 Array.map,因此像是 List, Array 都是functor,也就是存在一個合法的實作:

instance functorArray :: Functor Array where
map :: forall a b. (a -> b) -> (Array a -> Array b) map func = _

Array.map 的作用就是把原本容器 Array a 裡的數值取出,對每個數值應用給定函式 func,再把數值放到新的容器 Array b 裡。如果是在習慣使用mutation的指令式程式語言,常常需要手動寫下這些步驟,這個方法似乎只是提供一個比較方便的函式,但對函數式編程這才是最基礎的特性。同樣的操作也可以應用到 Map String, Set, Tree 等結構上,所以他們都是 Functor(除了 Set)。基於這個印象,若 f 為 Functor,f a 代表的是裝有類型為 a 的數值的容器,在這裡 f 可以是 List, Array, Map String, Tree 等類型的容器,而 a 可以是任意類型。事實上 a 必須是任意類型,因為 map 的定義用 forall a 讓呼叫端能夠使用任意類型,同時也限定了實作時必須編寫對任何類型 a 都適用的函式。也是因為如此 Set 才不能是 Functor,它的數值必須是可排序的。因此現在可以更新一下我們對於 Functor 的印象:若 f 為 Functor,f a 代表的就是可以裝有類型為 a 的數值的容器,且對於任何類型 a 都適用

其實 Set 也有自己的 map,只是它限定 func 的回傳類型必須是能夠排序的,這是因為它所裝的數值順序與結構有關。例如在實作上它可能是用二元樹實現的,而其結構必須由數值的順序決定,因此當我們應用一些會改變順序的函式作 map,它的結構就會發生改變。反過來說,Functor 的 map 不能根據 func 回傳的結果改變容器的結構,畢竟如果結果可以是任意類型,等於你不知道它的任何特性,也就不能根據它改變結構。如果不管 Set 二元樹的結構直接作 map,雖然它的確就能變成 Functor,但是卻也不再是 合法的 Set。就算使用其他實作方法也不能解決這個問題,畢竟只要應用 \_ -> 1 這種常數函式作 map,就能把任意大小的 Set 變成只有一個元素。那如果今天是 MultiSet (可以有重複元素的集合)是不是就沒這個問題了?理論上是沒錯,但是要在不知道元素順序的情況下是不可能有效率地實現的。如果不做排序當然可以沒有效率地實現 MultiSet,但它基本上沒有任何好處,你為何不乾脆直接使用 List?

Functor 不只限制 map 後結構不能根據 func 回傳的結果改變,事實上不管 func 為何結構都不能發生改變,就算 func 是 \a -> a 也一樣。這個限制可以透過以下規則描述:

map f >>> map g = map (f >>> g)

這裡的 >>> 是函式組合算子,它代表先應用前面的函式再應用後面的函式。這個規則說明先組合函式再做 map 的結果應該要跟分別做 map 一樣。從容器的角度來看,就是看你要在一次 for loop 就做完全部,還是分兩次做,直觀上內容物的確要是一樣的。這個規則的重點不在於內容物,而是容器本身,它告訴你 map 一次和 map 兩次沒有什麼不同,因此你不能在 map 的時候偷偷做其他事情,例如紀錄使用幾次 map,例如:

data MapCounter a = MapCounter Int a

map :: forall a b. (a -> b) -> (MapCounter a -> MapCounter b)
map f (MapCounter n a) = MapCounter (n + 1) (f a)

這個 map 就不是合法的 Functor.map。同樣地,對 List 做 map 時不能偷改數量和順序,對 Map String 做 map 時不能偷換 key,對 Tree 做 map 必須保持原本的樹結構不變。

這個規則可以透過一些「魔法」簡化成 map id = id 或是 map id a = a,也就是什麼都不做的 map 相當於什麼都不做。注意,我們規則都是寫成 = 而非 ==,這裡的 == 是由使用者定義的意義上的相等(typeclass Eq),而 = 則是代表與意義無關的完全相等(稱為外延性相等,在 haskell/PureScript 裡並沒有這種語法,它只是概念性的描述)。這代表這個規則的等號左右不僅是概念上相等的,它們實作細節上的結構也是相同的。例如 Map 的實作結構可能是未優化的二元搜尋樹,儘管兩個 Map 在樹結構上是不同的,它們仍可能作為 Map 是相等的,這個概念上的相等性可藉由實作 Eq 定義。但根據這個規則的限制,你甚至不能在 map 時偷偷整理樹結構,否則就會改變實作細節上的結構。你會在這類 typeclass 常看到這種限制,因為他根本不會去管參數在概念上的意義,它只關心規範上的一致性。而且「兩者是否在概念上相同」這件事是由 Eq 定義的,map 不會限制回傳值必須實作 Eq,實質上也有可能回傳如函式等不能比較的結果,也就無從根據這種相等性做限制。如果你真的想要只考慮概念上相等的 map,你應該自己定義一個 typeclass,並在說明文件上寫明白,不應直接使用原本的 typeclass。因為很多使用 Functor 的函式都是嚴謹地通過這些規則來證明正確性,而非透過思考概念上合不合理來判斷,胡亂地破壞規則會造成不可預期的錯誤。

前面對於 map 規則的描述代表容器本身的結構和內容物之間沒有依賴關係,否則 map 之後就會改變結構。或者說這個容器至少有一個不會改變結構的 map 方法,只看這個方法的話他確實是 Functor。現在讓我們看看有哪些容器屬於 Functor。List 是最容易理解的 Functor,map 的概念(可以看作)就是從這個方法延伸出來的;對於任意有實作 Ord 的類型 k,Map k 也是 Functor,對他做 map 不會改變順序,因為它的順序是由 k 決定的。事實上不論 k 是否實作 Ord,Map k 都是 Functor,畢竟做 map 不需要知道順序,然而要構造出 Map k 通常都需要知道 k 的順序,因此實質上它的確需要 Ord k。data Tree a = Tree a (List (Tree a)) 是 Functor,它在描述樹結構的同時夾帶了關聯數值,可以看作帶有樹結構的容器,其中的數值可以在不改變樹結構的情況下操作。Maybe 是 Functor,它是或許有值的容器,如果有值它才需要做 map。Identity 是 Functor,它是裝有一個數值的容器,其實就是數值本身而已。Const Unit 也是 Functor,它是沒有裝任何數值的容器。可以看到不論是裝有一系列數值的容器,以某個鍵值作為索引的容器,還是可能有值或為空的容器,抑或是根本不能裝任何數值的容器,這些都是 Functor。然而它們之所以是 Functor 並不只是因為它們是容器,容器的結構也不能依賴於內容物,例如 Set 就不是 Functor。整理一下目前為止對於 Functor 的印象:若 f 為 Functor,f a 代表的就是可以裝有類型為 a 的數值的容器,且容器的結構與內容物沒有依賴關係。然而 Functor 並非只能描述容器的特性,它代表的其實是「值」於「情境」的意義,下一篇文章將更深入討論 Functor。

    4會員
    28內容數
    這不是教你如何從物件導向到函數式編程的入門教程。我會深入探討物件導向與函數式編程的差異,並討論為什麼你應該使用函數式編程並徹底放棄物件導向。
    留言0
    查看全部
    發表第一個留言支持創作者!
    have bear的沙龍 的其他內容
    如果你曾經試圖學習函數式編程,並嘗試理解Monad,但看到文件上的定義卻一個字都看不懂,使用的術語、概念和一般常見的語言又很不一樣。網路上的教程往往都是以最簡單的範例試圖解釋Monad,但看到實際案例後又發現你完全不懂。事實上大部分教程的描述並不適用於「所有」的Monad,甚至在某方面來說是錯的,就
    如果你曾經試圖學習函數式編程,並嘗試理解Monad,但看到文件上的定義卻一個字都看不懂,使用的術語、概念和一般常見的語言又很不一樣。網路上的教程往往都是以最簡單的範例試圖解釋Monad,但看到實際案例後又發現你完全不懂。事實上大部分教程的描述並不適用於「所有」的Monad,甚至在某方面來說是錯的,就
    你可能也想看
    Google News 追蹤
    Thumbnail
    這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
    不怎麼完美的12月,難忘的不愛的抹茶蛋糕 自由飛翔的藝術和愛情,飛過了布魯克林的大橋,最後,永遠剩下藝術與她相伴,永遠不變的永恆,從小到大其他人是過客如浮雲。 她與他在唱歌痛快斯吼著,舉起手旋轉,說要地老天荒,多少次還是剩下她一個人與心碎。 身邊的男伴如浮雲來去,沒習慣也早已習慣,直到有天和她
    護理位在轉變的世代,而時代進步的路上,新舊觀念的衝突難免,但我們是否具備接受「改變」的勇氣?夾好乾淨的頭髮跟維持儀容就是展現專業形象,那帶著徹夜未眠的疲憊上班,想問疲勞駕駛跟給藥錯誤嚴重度是否相當? 當世代間的隔閡深化,那高聳金字塔腐壞跟停滯也是可預期的,實在難以壯大、難以改變,嘖嘖嘖。
    Thumbnail
    PM要我們分享一個自己去過最寒冷的國家或景點,到目前為止,我只出過兩次國,一次是去Okinawa,另一次是去大陸坐船遊長江三峽,但兩次出國都在夏天,一點也不冷,還熱得要命!所以我去過最寒冷的國家,竟然是——
    Thumbnail
    話說我在〈歲末年終感恩文:感謝一眾帥哥美女的相助與支持!〉裡,曾經深深深深懷疑薯友天老(一個人的天荒地老)有雙重人格,不然怎麼會平常都貼學術論文,可當起優質創作者評審時,又能以生花妙筆為一眾創作者寫出精采絕倫的專屬模板文?
    Thumbnail
    上一次在診間昏倒後,醫生懷疑可能和我的血糖低有關係(因為當時所有數據中只有這項有問題),所以安排我去內分泌科檢查。接著一連串的檢查...
    所以當我們遇到業力現前,懺悔業障,甘心忍受,一心念佛──道晟法師
      所以說,為什麼要長壽呢?   我現在正努力地賺錢,為了可以安心退休。
    Thumbnail
    不過找遍整個網路,談到「槓桿ETF報酬期望值」的人,除了我們之外,就只有PTT DAZE。
    Thumbnail
    採訪高管,不免擔憂多於興奮,憂心問答制式,或是與部門實際工作樣貌有著巨大落差。「來吧請坐!今天我們要聊聊什麼?」正與同仁聊天的文鎧副總抬起頭,像是家中爸爸一般微笑招呼,國泰置地廣場 27 樓的會議室,一瞬間成了溫馨客廳。
    Thumbnail
    關於「自己」這回事,在某個年紀就拋掉那些青少年時讀的那些關於自己的勵志暢銷書後,就沒再積極看市面上任何關於「自己」的任何書籍,但那種四肢無法伸展,總在人群裡不知道手腳怎麼擺的慌亂感,仍然時不時地從心裡蜿蜒出藤蔓布滿全身,讓人無法喘口氣,彷彿無時無刻都在提醒著「你得面對自己」。
    Thumbnail
    這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
    不怎麼完美的12月,難忘的不愛的抹茶蛋糕 自由飛翔的藝術和愛情,飛過了布魯克林的大橋,最後,永遠剩下藝術與她相伴,永遠不變的永恆,從小到大其他人是過客如浮雲。 她與他在唱歌痛快斯吼著,舉起手旋轉,說要地老天荒,多少次還是剩下她一個人與心碎。 身邊的男伴如浮雲來去,沒習慣也早已習慣,直到有天和她
    護理位在轉變的世代,而時代進步的路上,新舊觀念的衝突難免,但我們是否具備接受「改變」的勇氣?夾好乾淨的頭髮跟維持儀容就是展現專業形象,那帶著徹夜未眠的疲憊上班,想問疲勞駕駛跟給藥錯誤嚴重度是否相當? 當世代間的隔閡深化,那高聳金字塔腐壞跟停滯也是可預期的,實在難以壯大、難以改變,嘖嘖嘖。
    Thumbnail
    PM要我們分享一個自己去過最寒冷的國家或景點,到目前為止,我只出過兩次國,一次是去Okinawa,另一次是去大陸坐船遊長江三峽,但兩次出國都在夏天,一點也不冷,還熱得要命!所以我去過最寒冷的國家,竟然是——
    Thumbnail
    話說我在〈歲末年終感恩文:感謝一眾帥哥美女的相助與支持!〉裡,曾經深深深深懷疑薯友天老(一個人的天荒地老)有雙重人格,不然怎麼會平常都貼學術論文,可當起優質創作者評審時,又能以生花妙筆為一眾創作者寫出精采絕倫的專屬模板文?
    Thumbnail
    上一次在診間昏倒後,醫生懷疑可能和我的血糖低有關係(因為當時所有數據中只有這項有問題),所以安排我去內分泌科檢查。接著一連串的檢查...
    所以當我們遇到業力現前,懺悔業障,甘心忍受,一心念佛──道晟法師
      所以說,為什麼要長壽呢?   我現在正努力地賺錢,為了可以安心退休。
    Thumbnail
    不過找遍整個網路,談到「槓桿ETF報酬期望值」的人,除了我們之外,就只有PTT DAZE。
    Thumbnail
    採訪高管,不免擔憂多於興奮,憂心問答制式,或是與部門實際工作樣貌有著巨大落差。「來吧請坐!今天我們要聊聊什麼?」正與同仁聊天的文鎧副總抬起頭,像是家中爸爸一般微笑招呼,國泰置地廣場 27 樓的會議室,一瞬間成了溫馨客廳。
    Thumbnail
    關於「自己」這回事,在某個年紀就拋掉那些青少年時讀的那些關於自己的勵志暢銷書後,就沒再積極看市面上任何關於「自己」的任何書籍,但那種四肢無法伸展,總在人群裡不知道手腳怎麼擺的慌亂感,仍然時不時地從心裡蜿蜒出藤蔓布滿全身,讓人無法喘口氣,彷彿無時無刻都在提醒著「你得面對自己」。