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 * 某值
的結果都是某值[] ++ 某列表
的結果都是某列表
- 當有三個或更多引數時,無論在哪裡加括號改變執行順序,結果都一樣
(3 * 4) * 5
和3 * (4 * 5)
一樣([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,所以接直接跳過了。