IOがコンテナでありFunctorでもあるということ その1
モナドは「プログラム可能コンテナ」(by kazu_yamamotoさん, モナモナ言わないモナド入門 第2版)である、という考え方でIOを理解しようとしてみたのでちょっと書いてみる。
fmapとjoinによるモナドの定義
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のアの字も出てこないのでやきもきされていることとは思うが、長くなったので次回へ続く。