《Haskell趣學指南》筆記之 Monoid

方應杭在飢人谷發表於2019-05-11

Monoid 是一個型別類。 字典對 Monoid 的解釋是:

獨異點,帶有中性元的半群;

這應該是範疇論裡的東西,反正我目前是看不懂這個什麼群。

我們先學習 newtype

newtype 關鍵字

data 關鍵字可以建立型別; type 關鍵字可以給現在型別設定別名; instance 關鍵字可以讓型別變成型別類的例項;

newtype 關鍵字是根據現有資料型別建立新型別。 newtype 跟 data 很像,但是速度更快,不過功能更少,只能接受值構造器,值構造器只能有一個引數。

newtype CharList = CharList { getCharList :: [Char] } deriving (Eq, Show) 

ghci> CharList "this will be shown!" 
CharList {getCharList = "this will be shown!"} 
ghci> CharList "benny" == CharList "benny" 
True 
ghci> CharList "benny" == CharList "oisters" 
False 
複製程式碼

Monoid

我們先用 *++ 作比喻

  1. 它們都接受兩個引數
  2. 引數和返回值的型別都相同
  3. 存在一個這樣的值:作為引數時,返回值與另一個引數相同
    1. 1 * 某值 的結果都是某值
    2. [] ++ 某列表 的結果都是某列表
  4. 當有三個或更多引數時,無論在哪裡加括號改變執行順序,結果都一樣
    1. (3 * 4) * 53 * (4 * 5) 一樣
    2. ([1,2] ++ [3,4]) ++ [5,6][1,2] ++ ([3,4] ++ [5,6]) 一樣

再看 Monoid

一個 Monoid 的例項由一個滿足結合律的二元函式和一個單位元組成。

在 * 的定義中 1 是單位元,在++ 的定義中[] 是單位元。

class Monoid m where
    mempty :: m
    mappend :: m -> m -> m
    mconcat :: [m] -> m
    mconcat = foldr mappend mempty 
複製程式碼
  • mempty 是單位元
  • mappend 是二元函式,書中認為這個函式命名為 append 是不恰當的,因為它的作用並不是追加
  • mconcat 接受一個 m 列表,然後通過 mappend 將其中的所有元素合成一個值
  • mconcat 預設用 foldr 實現

所以大部分例項只需要定義 mempty 和 mappend 就行了。預設 concat 大部分時候都夠用了。

monoid 定律

mempty `mappend` x = x 
x `mappend` mempty = x 
(x `mappend` y) `mappend` z = x `mappend` (y `mappend` z) 
-- 並不要求 a `mappend` b = b `mappend` a,這是交換律
複製程式碼

Haskell 不會強制要求這些定律成立,所以開發者要自己保證。

Monoid 例項

  • 列表是 Monoid 例項
  • Int 是 Monoid 例項嗎?
    • Int 的加法滿足 monoid 定律,單位元是 0
    • Int 的乘法也滿足 monoid 定律,單位元是 1
    • 那麼 Int 應該以哪種方式成為 Monoid 例項?
    • 答案是都可以,這就要用到 newtype 關鍵字了

Product 和 Sum

Data.Monoid 匯出了 Product,定義如下

newtype Product a=Product{getProduct::a}
    deriving(Eq,Ord,Read,Show,Bounded)
複製程式碼

他的 Monoid 例項定義如下:

instance Num a=> Monoid ( Product a) where
    mempty=Product 1
    Product x `mappend` Product y= Product (x * y)
複製程式碼

使用方法:

ghci> getProduct $ Product 3 `mappend` Product 9
27
ghci> getProduct $ Product 3 `mappend` mempty
3
ghci> getProduct $ Product 3 `mappend` Product 4 `mappend` Product 2
24
ghci> getProduct.mconcat.map Product $ [3,4,2]
24
複製程式碼

Product 使得 Num 以乘法的形式滿足 Monoid 的要求。

Sum 則是用加法:

ghci> getSum $ Sum 2 `mappend` Sum 9
11
ghci> getSum $ mempty `mappend` Sum 3
3
ghci> getSum.mconcat.mapSum $ [1,2,3]
6
複製程式碼

書中還說了很多其他類似的例子。

不過我更關注的是 monad,所以接直接跳過了。

相關文章