◆ Data.Monoid
ghci で :i Monoidとやると、Data.Monoid で定義されているインスタンスがたくさん表示されてくるので、ソースを見ながら一個ずつ調べてみる。(「⇨」の左辺に ghci への入力、右辺に ghci の出力を書いてみた)■ Any、All
Bool値をとる Monoid。mempty::Any ⇨ Any {getAny = False} Any False `mappend` Any False `mappend` Any True ⇨ Any {getAny = True} mconcat [Any False, Any True, Any False] ⇨ Any {getAny = True}All はこれの反対で、類推は容易。
■ Ordering
これがちょっとおもしろい。LT、EQ、GTの三つの値があって、EQ の優先度が低く、LT と GTでは早い者勝ちという感じ。mempty::Ordering ⇨ EQ mconcat [LT,LT] ⇨ LT, mconcat [LT,EQ] ⇨ LT, mconcat [LT,GT] ⇨ LT mconcat [EQ,GT] ⇨ GT, mconcat [EQ,EQ] ⇨ EQ, mconcat [EQ,GT] ⇨ GT mconcat [GT,LT] ⇨ GT, mconcat [GT,EQ] ⇨ GT, mconcat [GT,GT] ⇨ GT mconcat [EQ,EQ,GT,LT] ⇨ GT
■ ()
() は、mempty、mappend、mconcat ともに ()■ [a]
これも大体想像通り。mconcat では空リストが無くなる。mempty::[Int] ⇨ [] [3] `mappend` [1,4] ⇨ [3,1,4] mconcat [[3,1],[],[4]] ⇨ [3,1,4]
■ First a、Last a
First は Maybe a 型の値を持つ Monoid で、Nothing 以外の値で早い者勝ち。Last はその逆。mempty::First Int ⇨ First {getFirst = Nothing} mconcat[mempty, First Nothing, First (Just 27), mempty, First (Just 18)] ⇨ First {getFirst = Just 27} mempty::Last Int ⇨ Last {getLast = Nothing} Last (Just 27) `mappend` Last (Just 18) ⇨ Last {getLast = Just 18} Last (Just 27) `mappend` mempty `mappend` Last (Just 18) `mappend` Last Nothing ⇨ Last {getLast = Just 18}
■ Endo a
なんだか endomorphism というものに関係があるらしい。wikipedia を引くと自己準同型とか自己射なるものの事らしいけど、正直、今のところよくからない。まあソースを読めば関数の合成に関係がある事はなんとなくわかるけど・・・instance Monoid (Endo a) where mempty = Endo id Endo f `mappend` Endo g = Endo (f . g) appEndo (Endo (*2) `mappend` mempty `mappend` Endo (+1)) 3 ⇨ 8 appEndo (mconcat [Endo (+3), Endo (+1), Endo (+4)]) 3 ⇨ 11Foldable のソースでも使われていたりする。
■ Sum a、Product a …(a は Num)
文字通り、Sum が足し算で、Product が掛け算。mempty::Sum Int ⇨ Sum {getSum = 0} Sum 3 `mappend` Sum 1 `mappend` Sum 4 ⇨ Sum {getSum = 8} getSum $mconcat [Sum 3, Sum 1, Sum 4] ⇨ 8 mempty::Product Int ⇨ Product {getProduct = 1} Product 3 `mappend` Product 1 `mappend` Product 4 ⇨ Product {getProduct = 12} mconcat [Product 3, Product 1, Product 4] ⇨ Product {getProduct = 12}
■ Maybe a …(a は Monoid)
中身が Monoid の Maybe が Monoid になるつう事なんだろうか。Just [2,7] `mappend` Just [1,8] ⇨ Just [2,7,1,8] Just EQ `mappend` Just LT ⇨ Just LT Nothing `mappend` (Just $Sum 1) ⇨ Just (Sum {getSum = 1}) Nothing `mappend` Just GT ⇨ Just GT
■ Dual a …(a は Monoid)
なんかひっくり返すらしい。「双対」というものに関係がありそうだけど、詳細はよくわからない。Foldable の foldl の実装で使われてた。mempty::Dual [Int] ⇨ Dual {getDual = []} Dual [3,1] `mappend` Dual [4,1] ⇨ Dual {getDual = [4,1,3,1]} mconcat [Dual [3], Dual [1], Dual [4]] ⇨ Dual {getDual = [4,1,3]}
■ (a -> b) …(b が Monoid)
Monoid を返す関数を Monoid として扱うって事か。:t mempty::Monoid b =>(Int->b) ⇨ mempty::Monoid b =>(Int->b) :: Monoid b => Int -> b (mempty::(a->String)) 333 ⇨ "" (mempty::(a->())) "hello" ⇨ () ((:"!") `mappend` (:"?")) '@' = "@!@?" ((compare 1) `mappend` (compare 10)) 5 ⇨ LT (((\a b->[a + b]) `mappend` (\a b->[a * b])) 3) 10 ⇨ [13,30]
■ (a, b...) …(a から最大 e まで Monoid)
Monoid を値に持つタプルを Monoid として見て、縦に並べて計算する感じ。5個分まで定義。
mempty:: (String, Maybe (Sum Int)) ⇨ ("",Nothing) ("Hello, ", Sum 0.3) `mappend` ("World!", Sum 0.014) ⇨ ("Hello, World!",Sum {getSum = 0.314}) (GT, Sum 0.3) `mappend` mempty ⇨ (GT, Sum {getSum = 0.3})
◆ 試しに書いてみる
mconcat はデフォルト実装があるので、mempty と mappend だけ書けばいいらしい(参考URL)。
ただし Monoid law に気をつける必要がある。mempty `mappend` x = x x `mappend` mempty = x (x `mappend` y) `mappend` z = x `mappend` (y `mappend` z)
とりあえず「範囲」を Monoidとして書いてみた。
mappend は 両方の範囲を含む最小の範囲とした。左辺が右辺より大きくなる Range も作れてしまうが、両辺ともに mempty でない場合の mappend で正規化するようにした。(mempty をふくむ mappend でこれをやると Monoid law が成立しなくなる)
data Range a = NullRange | Range (a, a) deriving (Show, Read, Eq) instance Ord a => Monoid (Range a) where mempty = NullRange NullRange `mappend` range = range range `mappend` NullRange = range range1 `mappend` range2 = let Range (l1, r1) = canonicalize range1 Range (l2, r2) = canonicalize range2 in Range (min l1 l2, max r1 r2) where canonicalize (Range (l,r)) = Range (min l r, max l r)GHCI に読ませてちょっと試してみる。
ghci> mempty::Range Int NullRange ghci> Range (3, 6) `mappend` Range (4, 10) Range (3,10) ghci> mconcat [Range (2,8), NullRange, Range (13,5), Range (5,5)] Range (2,13)でさらに、Writer に Monoid の制約があるので、これと組み合わせて使ってみる。
printRange :: (Ord a, Show a) => (Range a) ->WriterT (Range a) IO () printRange range = do tell range liftIO $ print range main = runWriterT $ do printRange $ Range ('g', 'u') printRange $ Range ('l', 'r') printRange $ Range ('t', 'w')printRange は、受け取った範囲を標準出力に書き出しながら、Writer にも出力する関数。これを連続的に実行すると、最終的に「ログ」の部分は全ての範囲を含む最小の範囲になる。
ghci> :main Range ('g','u') Range ('l','r') Range ('t','w') ((),Range ('g','w'))
◆ 雑感
Endo Monoid のところで、どうやら自己射/自己準同型というものに関係があるらしいと書いたけど、そもそも Monoid からして(もちろん Monad も)圏論周辺の数学用語らしいので、普通に Haskell を使う分には数学は不要とは言うけど、やっぱりある程度理解しておく必要はあるのかなと思う。
0 件のコメント:
コメントを投稿