かとじゅんの技術日誌

技術の話をするところ

Maybeを自作してみる(Monoid編)

今回は自作した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さんに教えてもらったのでソース読んでみる。

まず "そのまま" ってやつどうなっているのか。

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のように引数をとらない関数は多相定数というらしい。