Monoidのこと

昨日のポストで取り上げた変わった型のApplicativeの2つ目:

instance Monoid a => Applicative ( (,) a) where
	pure x = (mempty, x)
	(u, f) <*> (v, x) = (u `mappend` v, f x)

はMonoidが出てきます。今までかかわったことがなかったのですが、定義を見てみた感じでは、なんとなく何を言ってるかわかりそうなので、ここで取り組んでみようと思います。

Monoidのクラス定義は以下のとおり:

class Monoid a where
mempty :: a
mappend :: a -> a -> a
mconcat :: [a] -> a

ただし、mconcatはデフォルトのインプリメンテーションがmemptyとmappendを使った形で提供されているので、Monoidのクラスインスタンスを新しく定義する上で必須となるのはmemptyとmappendだけのようです。ちなみに、mconcatのデフォルト定義は:

mconcat = foldr mappend mempty

だそうです...カテゴリー理論は奥が深そうなので、今のところはパスして、あちこち読んだり、試してみた感じでは、Monoid型aは、a->a->a となる関数mappendが存在して、さらに、
mappend mempty x = x
mappend x mempty = x
となるようなmemptyが存在する型aについて定義されているようです…

何言ってんだか、よくわからないわけなんですが、例を挙げれば、整数における+なんていうのは、(+):: Int -> Int -> Intということで、mappendの条件を満たしていて、それに対応するmemptyは0ということですね…

整数では掛け算も(*)::Int -> Int -> Intですね...この場合のmemptyは1です。
割り算も、小数点以下を切り捨てる(div)::Int->Int->Intなんですが、これに対するmemptyはあるでしょうか?memptyはmappendの第一と第二パラメタのどちらに渡されてももう一つのパラメタの値を買えずに返さなくてはいけないので、`div`に対するmemptyはなさそうです。ということで、`div`を使ったMonoidのクラスインスタンスは作れなさそうです。

ちなみに、Haskellでは+に関するMonoidのクラスインスタンスはSumという型、*はProductという型でインプリメントされているようです。

たちかえって、mconcatをみれば、foldr mappendしているわけなので、リストの中身を全てmappendでまとめて一つの値にしているということですね…ためしにサンプルを書いてみると:

module Main
	where
	
import Data.Monoid

toSum a = Sum a
toProd a = Product a

mfold f xs = mconcat $ map fxs

test = do
	print.getSum $ mfold toSum [1, 2, 3, 4, 5]
	print.getProduct $ mfold toProd [1, 2, 3, 4, 5]
	
=>
	15
	120

みたいな感じです。

ほかにもMonoidのクラスインスタンスには:

Monoid All -- (mappend = &&::Bool, mempty = True)
Monoid Any -- (mappend = ||::Bool, mempty = False)
Monoid Ordering
Monoid b => Monoid (a -> b)
Monoid a => Monoid (Dual a)
Monoid (Endo a)
Monoid (First a)
Monoid (Last a)
Monoid a => Monoid (Maybe a)
Monoid [a] -- (mappend = ++, mempty = [])

(Monoid a, Monoid b) => Monoid (a, b) 
(Monoid a, Monoid b, Monoid c) => Monoid (a, b, c)
(Monoid a, Monoid b, Monoid c, Monoid d) 
   => Monoid (a, b, c, d)
(Monoid a, Monoid b, Monoid c, Monoid d, Monoid e) 
   => Monoid (a, b, c, d, e)

ということで、気づくのは、Monoidのクラスインスタンス型を含むタプル(要素数2−5)は、自動的にMonoidのクラスインスタンスになるようです。

あとは、sortWithで使うcompareの返り値でもあるOrderingもMonoidで、

mempty = eq
mappend eq a = a
mappend GT _ = GT
mappend LT _ = LT

と、[x1, x2, x3, x4]であらわせるベクター値のリストをソートしようと思ったら、

module Main
	where

import Data.List
import Data.Monoid

sortVector :: (Ord a) => [ [a] ] -> [ [a] ]
sortVector xs = sortBy (\x y -> 
      mconcat $ zipWith (compare) x y) xs

vector = [ [1, 2, 3, 4], [1, 5, 9, 7], [7, -1, 4, 0], [0, 9, 10, 5] ]
main = print $ sortVector vector

みたいなことができたりします。ちなみに、sortVectorは覚えたてほやほやのApplicativeを使えば:

sortVector :: (Ord a) => [ [a] ] -> [ [a] ]
sortVector xs = sortBy (\x y -> 
      mconcat $ (compare) <$> x <*> y) xs

と書き換えられます。x <$> y は pure x <*> yのショートハンドです。
そのほかを見てみれば、Monoid a => Monoid (Maybe a)というのがあって、

mempty = Nothing
mappend Nothing a = a
mappend a Nothing = a
mappend Just a Just b = Just (mappend a b)

ということで、Nothingの混じったリストの中からJustな値だけをまとめることがmconcat一発でできそうです。

ということで、覚えておくと便利そうですね...

ではでは...