IOがコンテナでありFunctorでもあるということ その1

モナドは「プログラム可能コンテナ」(by kazu_yamamotoさん, モナモナ言わないモナド入門 第2版)である、という考え方でIOを理解しようとしてみたのでちょっと書いてみる。

fmapとjoinによるモナドの定義

Haskell 2010でのMonadクラスの定義

class  Monad m  where  
    (>>=)  :: m a -> (a -> m b) -> m b
    return :: a -> m a

となっている。一方で、Philip Wadlerによる1992年初出の論文、Monads for functional programmingでは、以下のような定義も書かれている(p12)。(この論文ではbind演算子(>>=)が⋆, return相当の関数がunitという名前になっているので、適宜読み替える)

 map  :: (a -> b) -> m a -> m b
 map f m = m >>= \a->return (f a)
 join :: m (m a) -> m a
 join z = z >>= \m->m

ぱっと見、何がなんだかよくわからないが、ここでいうmapはシグネチャをみてわかるとおりFunctorクラスのfmapと同じモノである。

class  Functor f  where  
    fmap :: (a -> b) -> f a -> f b

以下、mapを(Listのmapと区別する意味で)fmapと読み替える。そうすると

 fmap :: (a -> b) -> m a -> m b
 fmap f m = m >>= \a->return (f a)
 join :: m (m a) -> m a
 join z = z >>= \m->m

となる。ひとまず、ここで定義したfmapがFunctorにおけるfmapと同じ意味になるかをListで確認してみよう。Listにおける(>>=), returnは

instance  Monad []  where  
    m >>= k      = concat (map k m)  
    return x     = [x]  
    fail s       = [] 

であるから、

 fmap f m = m >>= \a->return (f a)
          = concat (map (\a->return (f a)) m)
          = concat (map (\a->[f a]) m)
-- map (\a->[f a]) [m0,m1,...] = [[f m0],[f m1],..]
-- concat [[f m0],[f m1],...] = [f m0,f m1,...]
          = map f m

となって、めでたくListにおけるfmapはmapと同じであると導けた。
では、joinとはなんじゃらほい。

join : 二重構造を潰す演算子

上述のとおり、joinのシグネチャは "m (m a) -> m a" となっている。これも先ほどと同じくListを例に意味を確認してみよう。Listにおける(>>=)の定義を用いて展開すると、

 join :: [[a]] -> [a]
 join z = z >>= \m->m
        = concat (map (\m->m) z)
        = concat (map id z)
        = concat (id z)
        = concat z

となって、join = concat となってしまった。
ここでListモナドの定義を振り返ってみると

    m >>= k      = concat (map k m)  
-- fmap = map, join = concat だから
    m >>= k      = join (fmap k m)

と書ける。実はこの定義が、Listに限らず他のモナドでも同様に使えることを以下で確認していく。

モナド = 二重構造を作って潰すこと」と理解してみる

Listにおける(>>=)の仕事は、

  • k :: a -> [b] をfmapして [a] から二重リスト [[b]] を作り、
  • join :: [[b]] -> [b] で二重リストを一重リストに潰す

ということだった。これが別のモナド、例えばMaybeではどうなるかを見てみよう。
Maybeのfmapの定義は

 instance  Functor Maybe  where  
    fmap f Nothing    =  Nothing  
    fmap f (Just x)   =  Just (f x)

となっている。(Haskell 2010 Standard Prelude)ではこれに合わせてjoinを定義してみよう。

 join :: Maybe (Maybe a) -> Maybe a
 join Nothing          = Nothing
 join (Just (Nothing)) = Nothing
 join (Just (Just x))  = Just x
 -- ∴ join (Just x)   = x

これを、joinとfmapによる(>>=)の定義に適用してみる。

 -- m >>= k      = join (fmap k m)
 Nothing   >>= k = join (fmap k Nothing)
                 = join Nothing
                 = Nothing
 (Just x)  >>= k = join (fmap k (Just x))
                 = join (Just (k x))
                 = k x

となり、めでたくMaybeモナドの定義

instance  Monad Maybe  where
    (Just x) >>= k   =  k x
    Nothing  >>= k   =  Nothing

と一致した。
読者におかれましてはまったくIOのアの字も出てこないのでやきもきされていることとは思うが、長くなったので次回へ続く。