リストはモノイド
インスタンス宣言
instance Monoid [a] where mempty = [] mappend = (++)
リストは中身の型がなんであっても常に Monoid のインスタンスにできる。
*Main> [1,2,3] `mappend` [4,5,6] [1,2,3,4,5,6] *Main> ("one" `mappend` "two") `mappend` "tree" "onetwotree" *Main> "one" `mappend` ("two" `mappend` "tree") "onetwotree" *Main> "one" `mappend` "two" `mappend` "tree" "onetwotree" *Main> "pang" `mappend` mempty "pang" *Main> mconcat [[1,2],[3,6],[9]] [1,2,3,6,9] *Main> mempty :: [a] []
Product と Sum
数をモノイドにする方法として、
*
を二項演算にして 1
を単位元とする方法の他に、
+
を二項演算にして 0
を単位元とする方法がある。
*Main> 0 + 4 4 *Main> 5 + 0 5 *Main> (1 + 3) + 5 9 *Main> 1 + (3 + 5) 9
足し算もモノイド則が成り立つ。
*
と +
ともにモノイド則を満たしているが、
ある型に対して同じ型クラスのインスタンスを複数定義したい場合、
newtype
に包んで新しい型をインスタンスにする方法がとれる。
Data.Monoid
モジュールはこのために Product
と Sum
という型をエクスポートしている。
newtype Product a = Product { getProduct :: a } deriving (Eq, Ord, Read, Show, Bounded)
Product
の Monoid インスタンスはこのようになる
instance Num a => Monoid (Product a) where mempty = Product 1 Product x `mappend` Product y = Product (x * y)
すでに Num のインスタンスであるようなすべての型 a について、 Product a は Monoid のインスタンスになる。
*Main Data.Monoid> getProduct $ Product 3 `mappend` Product 9 27 *Main Data.Monoid> getProduct $ Product 3 `mappend` mempty 3 *Main Data.Monoid> getProduct $ Product 3 `mappend` Product 4 `mappend` Product 2 24 *Main Data.Monoid> getProduct . mconcat . map Product $ [3,4,2] 24
Sum も同様
*Main Data.Monoid> getSum $ Sum 2 `mappend` Sum 9 11 *Main Data.Monoid> getSum $ mempty `mappend` Sum 3 3 *Main Data.Monoid> getSum . mconcat . map Sum $ [1,2,3] 6
Any と All
Num a と同じようにモノイドにする方法が2通りあるものは他にもある。
Bool
は ||
をモノイド演算として、False
を単位元とする方法。
||
は論理和を表し、2つの引数のいずれかが True なら True を返す。
newtype
で Any
が定義されている。
newtype Any = Any { getAny :: Bool } deriving (Eq, Ord, Read, Show, Bounded)
インスタンス定義
instance Monoid Any where mempty = Any False Any x `mappend` Any y = Any (x || y)
Any
と呼ばれるのは、
x `mappend` y
が、x か y のいずれか(any)が True であった場合に、True になるから。
*Main Data.Monoid> getAny $ Any True `mappend` Any False True *Main Data.Monoid> getAny $ mempty `mappend` Any True True *Main Data.Monoid> getAny . mconcat . map Any $ [False, False, False, True] True *Main Data.Monoid> getAny $ mempty `mappend` mempty False
もう1つの方法は、
&&
をモノイド演算として、True を単位元とする方法。
newtype All = All { getAll :: Bool } deriving (Eq, Ord, Read, Show, Bounded)
インスタンス定義
instance Monoid All where mempty = All True All x `mappend` All y = All (x && y)
確認
*Main Data.Monoid> getAll $ mempty `mappend` All True True *Main Data.Monoid> getAll $ mempty `mappend` All False False *Main Data.Monoid> getAll . mconcat . map All $ [True, True, True] True *Main Data.Monoid> getAll . mconcat . map All $ [True, True, False] False
2つの引数がともに True である場合に限り True を返す。
Ordering モノイド
ものを比較した結果を返すのに使う。
LT
, EQ
, GT
という3つの値のいずれかをとる。
インスタンス宣言はこう
instance Monoid Ordering where mempty = EQ LT `mappend` _ = LT EQ `mappend` y = y GT `mappend` _ = GT
文字列を辞書順で比較するときのルールに合わせた定義になっている。
EQ
が単位元になっているのは、2つの単語の同じ位置に同じ文字を挿入しても辞書順は変化しないから。
Ordering
の Monoid インスタンスでは
x `mappend` y
は、
y `mappend` x
と一致しない。左辺がEQで無い限り左辺が優先されるため。
2つの文字列を引数にとり、その長さを比較して Ordering を返す関数を書く。
lengthCompare :: String -> String -> Ordering lengthCompare x y = let a = length x `compare` length y b = x `compare` y in if a == EQ then b else a
Monoid を使って書く
import Data.Monoid lengthCompare :: String -> String -> Ordering lengthCompare x y = (length x `compare` length y) `mappend` (x `compare` y )
実行
*Main Data.Monoid> lengthCompare "zen" "ants" LT *Main Data.Monoid> lengthCompare "zen" "ant" GT
単語の中から母音の数も比較し、それを2番めに重要な条件にする。
import Data.Monoid lengthCompare :: String -> String -> Ordering lengthCompare x y = (length x `compare` length y) `mappend` (vowels x `compare` vowels y) `mappend` (x `compare` y ) where vowels = length . filter (`elem` "aeiou")
実行
*Main Data.Monoid> lengthCompare "zen" "anna" LT *Main Data.Monoid> lengthCompare "zen" "ana" LT *Main Data.Monoid> lengthCompare "zen" "ann" GT
Maybe モノイド
Maybe a も複数の方法でモノイドになれる。
1つ目の方法。型引数 a がモノイドであるときに限り Maybe a もモノイドであるとし、 Maybe a の mappend を、Just の中身の mappend を使って定義する。Nothing を単位元とする。
instance Monoid a => Monoid (Maybe a) where mempty = Nothing Nothing `mappend` m = m m `mappend` Nothing = m Just m1 `mappend` Just m2 = Just (m1 `mappend` m2)
使う
*Main Data.Monoid> Nothing `mappend` Just "andy" Just "andy" *Main Data.Monoid> Just LT `mappend` Nothing Just LT *Main Data.Monoid> Just (Sum 3) `mappend` Just (Sum 4) Just (Sum {getSum = 7})
失敗する可能性のある計算の返り値をモノイドとして扱いたい場合に便利。
Maybe の中身が Monoid インスタンスでなかった場合、mappend
は使えない。
そこで First a という型を使う。
値に Maybe a が強制される。
newtype First a = First { getFirst :: Maybe a } deriving (Eq, Ord, Read, Show)
インスタンス宣言
instance Monoid (First a) where mempty = First Nothing First (Just x) `mappend` _ = First (Just x) First Nothing `mappend` x = x
実行
*Main Data.Monoid> getFirst $ First (Just 'a') `mappend` First (Just 'b') Just 'a' *Main Data.Monoid> getFirst $ First Nothing `mappend` First (Just 'b') Just 'b' *Main Data.Monoid> getFirst $ First (Just 'a') `mappend` First Nothing Just 'a' *Main Data.Monoid> getFirst . mconcat . map First $ [Nothing, Just 9, Just 10] Just 9
First は、いくつもある Maybe 値にどれか1つでも Just があるか調べたい時に便利。
Last もある。
*Main Data.Monoid> getLast . mconcat . map Last $ [Nothing, Just 9, Just 10] Just 10 *Main Data.Monoid> getLast $ Last (Just "one") `mappend` Last (Just "two") Just "two"
Nothing でない最後の値が採用される。
所感
Ordering と Maybe の2項演算子については+
, *
と違って具体的な演算子が無い?のでわかりにくい。
あと、First が出てきたところで、
1つの選択は、第一引数を返して第二引数は捨てる、と決めておくことです。
この文章の意味が理解できていない。

- 作者: MiranLipovaca
- 出版社/メーカー: オーム社
- 発売日: 2017/07/14
- メディア: Kindle版
- 購入: 4人 クリック: 9回
- この商品を含むブログを見る