iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 24
1
Software Development

mostly:functional 從零開始的異世界程式觀 --- 函數式程式設計的試煉系列 第 24

mostly:functional 第二十三章:Monoid 的 Monoid

延著走道往下,我走過一個又一個的房間。忽然有一種奇怪的念頭浮上,我感覺其實每個房間裡的鏡子雖然看似迥異,但其實一直都是同一面鏡子。它只是跟隨著我,不斷的移動到下個房間裡。而這面鏡子會依它所在的地方,變換其樣貌…

看似走到建築物的底端,但等在走道盡頭的,卻是一座樓梯。

居然還要去樓上…




Maybe 是一種 Monoid 嗎?

這個嘛…看情況。Maybe 也可以是一種 Monoid,但是有條件。那個條件就是當這個 Maybe 裡裝的東西,是一個 Semigroup 的時候,那麼就可以推導出這個 Maybe 的 Monoid 性質。

-- Haskell 語法
-- Maybe Monoid 的型別宣告
instance Semigroup a => Monoid (Maybe a)

試試看吧:

如果兩個 Maybe Monoid <> 在一起的時,兩邊都是有值的 Just,那麼就會用裡面的值(記得它是 Semigroup 嗎?) 的 <> 的方式併在一起,再裝回 Just 裡。

而如果任何一邊是 Nothing 時,那就忽略 Nothing

-- Haskell 語法
Just (Sum 1) <> Just (Sum 2) -- => Just (Sum {getSum = 3})

Just (Sum 1) <> Nothing -- => Just (Sum {getSum = 1})

不過當我們說到忽略 Nothing 這件事的時候,請記得這是 Haskell 在 Maybe Monoid 上實作的行為。Maybe 的 Monoid 有其它可能,只是沒有實作出來。而這個可能,我們會在其它 typeclass 上看到。

元組是一種 Monoid 嗎?

要讓元組是 Monoid,也有它的條件。跟 Maybe 的情況類似,元組要成為 Monoid,那麼它裡面的成員也都要是 Monoid 才行:

-- Haskell 語法
-- 元組們的 Monoid 的型別宣告
instance (Monoid a, Monoid b, Monoid c, Monoid d, Monoid e) =>
         Monoid (a, b, c, d, e)

instance (Monoid a, Monoid b, Monoid c, Monoid d) =>
         Monoid (a, b, c, d)
 
instance (Monoid a, Monoid b, Monoid c) => Monoid (a, b, c)

instance (Monoid a, Monoid b) => Monoid (a, b)

-- 試試看
(Sum 1, Product 2) <> (Sum 2, Product 3) 
-- => (Sum {getSum = 3},Product {getProduct = 6})

布林值是一種 Monoid 嗎?

在這裡讓你停下來想一下。

其實跟整數的情況一樣,可以運用在布林值上,符合封閉律與結合律的二元運算也不只一個。所以就像整數有 SumProduct 一樣。布林值也有 AllAny 這兩個型別:

-- Haskell 語法
(All True) <> (All False) <> (All True)  -- => All {getAll = False}

(Any False) <> (Any False) <> (Any True) -- =>Any {getAny = True}

順帶一提,還有 FirstLast 這兩個可以做用在 Maybe 上的 Monoid 也蠻有趣的。


那麼,我們要來問一個很重要的問題了: 函式,也是一種 Monoid 嗎?

函式是一種 Semigroup 嗎?

我們可以先試著證明函式是不是一種 Semigroup。方法就是找到一個二元運算(或幾個函式的組合成的二元運算),可以符合 1. 封閉律 及 2. 結合律。

想到了嗎?接收兩個函式,回傳一個函式的東西………

是的。 .

函式的組合就是一種符合封閉律與結合律的二元運算。我們可以證明一下:

-- Haskell 語法
f = (+ 1)
g = (* 2)
h = (+ 3)

cf1 = (f . g) . h
cf2 = f . (g . h)

cf1 1 == cf2 1

函式是一種 Monoid 嗎?

那麼看來似乎我們只要找到一個跟任何函式 . 組合在一起時,能讓該函式依然是函式自己的函式就好了 (這句有點難唸,希望你腦袋裡的聲音沒有咬到舌頭。)

Haskell 的匿名函式

得益於 Haskell 函式的天生懶惰特性,我們至今都還沒在 Haskell 裡用過匿名函式。但是 Haskell 裡其實是有匿名函式的。它的語法如下:

-- Haskell 語法
\x -> x + 1

參數的前方要加一個反斜線 \,據聞是因為這看起來跟 https://chart.googleapis.com/chart?cht=tx&chl=%5Clambda 很像,只是少了左邊的腳而已(哈哈)。當然,它也完全符合一等公民的所有性質,可以指派給變數,當做呼叫其它函式的引數……你知道的。

沒作用的函式?

那個跟任何函式組合,而不會造成任何改變的函式,長得像這樣:

-- Haskell 語法
\x -> x

接收到一個參數,並原封不動的回傳的函式,我們稱之為恆等函式。而這個函式雖然看起來什麼事都沒做,但其實它有許多有趣而且非它不可的用法。因此 Haskell 裡其實就內建了這個函式,叫做 id

-- Haskell 語法
id 1 -- => 1
id True -- => True

等一下,其實還有一個前題:

當我們知道 id 這個函式,就是函式的 mempty 之後,要證明函式也是 Monoid,我們還少考慮了一件事,那就是當兩個函式合併之後,這兩個函式的回傳值要怎麼合併在一起?想一下吧。

就跟上面的幾個型別一樣,函式要成為 Monoid 的條件,就是這兩個函式的回傳值,也要是同型別的 Monoid 才行:

-- Haskell 語法
-- 內建:函式 Monoid 的實作
instance Monoid b => Monoid (a -> b) where
    mempty _ = mempty
    mappend f g x = mappend (f x) (g x)

-- 用用看!
f x = Sum x * 10
g x = Sum x - 1

h = f <> g
h 20 -- => Sum {getSum = 219}

那麼,我們是不是可以在串列裡,放進一堆函式,只要這些函式的回傳值是同類型的 Monoid,那麼就可以把它們用 fold (也就是 reduce) 組合成一個函式了?




終於我看到了一個鑲嵌著大塊板子的平台,而這次沒有東西擋在上面了。仔細看了一下,上面寫著……

[to be continue]


上一篇
mostly:functional 第二十二章:Monoid 的實體
下一篇
mostly:functional 第二十三章的試煉:Monoid 的證明*
系列文
mostly:functional 從零開始的異世界程式觀 --- 函數式程式設計的試煉35

尚未有邦友留言

立即登入留言