HaskellにはMonoidという型クラスがあります
モノイドというのは結合則を満たす演算子と単位元が存在する集合のことで
- 自然数と足し算
- 自然数と掛け算
- 文字列の結合
などここには書き表せられないほどたくさんのものがモノイドの性質を満たします。
class Monoid a where
mempty :: a
mappend :: a -> a -> a
mconcat :: [a] -> a
mconcat = foldr mappend mempty
ところで整数の足し算と掛け算もどちらもモノイドなのですがHaskellのIntはどのようなMonoid のインスタンスになっているでしょうか?
実はIntはそもそもMonoidのインスタンスになっていません
その代わり Data.Monoid
では Sum
とProduct
という二つの型が newtype
で宣言されていてそれらがMonoidのインスタンスとなっています。
newtype Sum a = Sum { getSum :: a }
instance Num a => Monoid (Sum a) where
mempty = Sum 0
(Sum x) `mappend` (Sum y) = Sum (x + y)
newtype Product a = Product { getProduct :: a }
instance Num a => Monoid (Product a) where
mempty = Product 1
(Product x) `mappend` (Product y) = Product (x * y)
こうすることで
>>> (Sum 5) `mappend` (Sum 5)
Sum 10
のように無事 Int (Num a)にもモノイドの性質を付与することが出来ました
でもちょっと待って下さい
使うたびにいちいち Sum
や Product
で包まなきゃいけないのでしょうか?
実際はもちろんそうなのですがそれほど面倒なわけでもありません
例えばsum
とproduct
を再実装してみましょう
sum' = Num a => [a] -> a
sum' = getSum . mconcat . fmap Sum
product' = Num a => [a] -> a
product' = getProduct . mconcat . fmap Product
どうでしょうか
変換が必要なのは入り口と出口だけであることがわかると思います
とっても便利ですね