Monoid
厳密に言うと、掛け算の重要な性質である結合法則
(a * b) * c = a * (b * c)
を満たし、さらに
a * 1 = 1 * a = a
となる
1
のような値(単位元)を持つ型。単位元を持たない場合は
Semigroup。
haskellclass Semigroup a where
(<>) :: a -> a -> a
class Semigroup a => Monoid a where
mempty :: a
-- 非必須メンバは略
該当するものは非常に多く、たとえば整数や実数は足し算、掛け算どちらもモノイドとなるし、Boolは論理和も論理積もモノイドである。このため、 Int
や Bool
は直接 Monoid
にはなっておらず、newtypeでラップした Sum
や Any
の Monoid
インスタンスを使用する事になる。
モノイドの便利な使い方
fold, foldMap
haskellghci> import Data.Foldable
ghci> fold ["Long", "Cat", "Is", "Long"]
"LongCatIsLong"
foldr (<>) mempty
または foldl (<>) mempty
と同じ意味なのだが、「要素ゼロ個の値がある」「foldrでもfoldlでも同じ値になる」ということが保障されるため文法的ノイズが非常に少なくなる。リストに限れば mconcat
と同じ意味。
直接モノイドになっていない型でも foldMap
を使えば行ける。
haskellghci> getAll $ foldMap All [True, False, False]
False
ghci> getAny $ foldMap Any [True, False, False]
True
Writer
任意のモノイドはWriterを被せるとモナドになるので、do文で書ける。
haskell print $ execWriter $ do
tell "Long"
tell "Cat"
tell "Is"
tell "Long"
リストで書いて mconcat
でつなげるケースの方が多いか。
Writerはパフォーマンスの問題があるので、新しめのtransformersパッケージに入っている
CPS版を使おう。Strictなら大丈夫、ではない。
主なモノイド
Sum
haskellghci> getSum $ foldMap Sum [1..5]
15
Product
haskellghci> getProduct $ foldMap Product [1..5]
120
Any
haskellghci> getAny $ foldMap Any [True, False, False]
True
All
haskellghci> getAll $ foldMap All [True, False, False]
False
列
haskellmconcat [[1,2],[3],[4,5]]
[1,2,3,4,5]
リストのように、結合にコストの掛かる構造だと、パフォーマンス問題が出る場合があるので注意。
文字列
haskell ghci> fold ["Long", "Cat", "Is", "Long"]
"LongCatIsLong"
StringやTextのように結合にコストの掛かる構造だと(ry
Endo
Endomorphism。 a -> a
の関数はモノイドである。
haskellghci> appEndo (foldMap Endo [abs,(*2),(+1),negate]) 2
2 -- abs ((*2) ((+1) (negate 2)))
どっちの順番で合成するのか分からなくなりがちだが、リストで書いたとき右にあるものを先に適用する順になるようだ。
haskellghci> dlist = Endo
ghci> dlistToList l = appEndo l []
ghci> dlistToList $ foldMap dlist [([1,2,3]++), ([4,5]++),([6]++)]
[1,2,3,4,5,6]