今回は自作したMaybe用のMonoid型クラスの実装を作ってみます。
Monoidって何ってさっぱりわからないので、型クラスの定義を見てみます。
class Monoid m where mempty :: m mappend :: m -> m -> m mconcat :: [m] -> m mconcat = foldr mappend mempty
mconcatはデフォルト実装があるようなので、memptyとmappendを実装する感じかな。memptyはそのMonoidの単位元を表す。mappendは二項演算子で同じ型の引数を二つ取り、同じ型の値を返す関数です。*1
MaybeのMonoidはちょっと難しいので先にリストのMonoid実装を見てみる。
instance Monoid [a] where mempty = [] mappend = (++)
早速試す。
Prelude> import Data.Monoid Prelude Data.Monoid> [1..3] `mappend` [4..6] [1,2,3,4,5,6] Prelude Data.Monoid> "hoge" `mappend` mempty "hoge" Prelude Data.Monoid> mconcat [[1..3], [4..6]] [1,2,3,4,5,6]
最後のmconcat [[1..3], [4..6]]
は、mconcatのデフォルト実装を見ればわかるけど、foldr (++) [] [[1..3],[4..6]]
と同じ意味になります。
前置き長くなったけど、自作したMaybeにもMonoid対応。コードは次のとおり。
import Data.Monoid import Control.Applicative data Option a = None | Some a deriving (Eq, Ord, Read, Show) instance Functor Option where fmap f (Some x) = Some (f x) fmap f None = None instance Applicative Option where pure = Some None <*> _ = None Some f <*> other = fmap f other instance Num a => Monoid (Option a) where mempty = None None `mappend` o = o o `mappend` None = o (Some x) `mappend` (Some y) = Some (x + y) getOrElse :: Option a -> a -> a getOrElse None v = v getOrElse (Some x) _ = x main = do let op1 = Some (1) `mappend` Some (2) let op2 = None `mappend` Some (2) let op3 = Some (1) `mappend` None let op4 = Some (1) `mappend` None `mappend` Some (2) print (op1, op2, op3, op4)
とりあえず型引数aをNumにしました。
instance Num a => Monoid (Option a) where
memptyはNoneを返します。
None `mappend` o = o
o `mappend` None = o
oはSomeしかないのでそれを返します。
(Some x) `mappend` (Some y) = Some (x + y)
aがNumの場合はx + yができる。
ということで一応できたけど、Num以外は対応できていないので、そろそろGHCのソース見ないと無理感。
xuwei_kさんに教えてもらったのでソース読んでみる。
@j5ik2o ソースはこのあたりですかね github.com/ghc/packages-b… Maybeって①そのままと②First③Lastと、3種類Monoidにする方法があって・・・とか、ちょっとだけややこしくなってます。たぶん「すごいH本」にもそのあたりの説明載ってるはず
— Kenji Yoshidaさん (@xuwei_k) 12月 10, 2012
まず "そのまま" ってやつどうなっているのか。
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)
おおお、型引数であるaがNumとかじゃなく、それ自体がMonoidであるとしていますね。であれば、Just (m1 `mappend` m2)
と記述できますな。なるほど。
というわけで、同じように書き換えると次のとおり。
instance Monoid a => Monoid (Option a) where mempty = None None `mappend` o = o o `mappend` None = o (Some x) `mappend` (Some y) = Some (x `mappend` y)
とりあえず、長くなったので今回はこのへんで。
車輪の再発明は学習用途にはうってつけですね!
*1:memptyのように引数をとらない関数は多相定数というらしい。