Monad を数学っぽく理解してみる。

Monad についてのまとめてみた。(間違ったことをいっているかもしれないけど。)

* Monad m は Applicative Functor で以下を満たすもの

  *  以下の関数をもつ。

    1.  (>>=) :: m a -> (a -> m b) -> m b
    2. (>>) :: m a -> m b -> m b
    3. return :: a -> m a
    4. fail :: String -> m a

  * monad 則をみたす。

 

   m a --------------------------> m b

      ↑                                        ↑    

     a  --------------------------->  b

 

return = pure となる。

Functor では a -> b を Fa -> Fb に

Applicative Functor では F(a->b) を Fa->Fb に導入できた。

Monad では a -> mb の関数を >>= によって ma -> mb に導入できる。(bind)

(>>= はたぶん記述しやすさのため ma ->(a->mb)->mb になっているが、

 (a->mb) -> ma -> mb と同じことのはず。)

 

はじめ Applicatvie Functor の <*>  は上記 Monad の定義から導き出せるのだと思っていた。そのため Monad は Applicative Functor だと言っているのだと。

その証明には g ∈ m(a->b) から a -> mb を作れればよいだけなので簡単にできるかなと思いきや、 g から a -> mb をどうにも作れなかった。

まあ本来 (Applicative m) => Monad m って型制約があればすむ話なんで、「Monad は Applicative Functor です」っていうのは Monad の全インスタンスは Applicative Functor としての実装もあるってことなのかも。

 

Monad を理解するためまた具体例で考えてみる。

 m = [ ] , a=b=Int

    [Int] -------------------------->  [Int]

      ↑                                       ↑    

     Int  --------------------------->  Int

>>= を使って Int -> [Int] を [Int]->[Int] に導入してみる。

 

Prelude> [2,3,4,5] >>= (\x->return(x*3))
[6,9,12,15]

    [Int] -------------------------->  [Int]   ∋ return(x*3)

      ↑                                       ↑            ↑

     Int  --------------------------->  Int         |

       x  ------------(*3)------------> x  ------+

 

Int -> [Int] をつくるのに a->b->mb と b を介している。最後の return は b -> mb のため。

 

>>= の引数定義により

Prelude> [2,3,4,5] >>= (\x->return(x*3)) >>= (\x->return(x+2)) >>= (\x->return(x-3))
[5,8,11,14]

のように連続して a->mb を適用できる。

 

 * >>= の何がうれしいのか?

m の先の世界 m a はいわゆる箱の中の世界。 m a の中身の詳細はこちらがわの人間はしらない。

でもせめて「こっち側と関連している部分」くらいは箱の中から値を取り出して操作をすることができる。それが Monad

「こっち側と関連している部分」というのはどこかというと m a の部分集合で return a の行き先となる部分。

 

Monad 則では

x ∈ a , f ∈ (a -> m b) とすると

   (return x) >> f = fx

なので return によって m a に移される部分では m a の元を a の元として抜き出して m b に送るというコードがかける。

例えば

Maybe Int ---------> Maybe Int

     ↑                               ↑

    Int --------------------> Int

 

Prelude> (Just 3) >>= \x -> return (3*x)
Just 9

Monad 則によると Just 3 = (return 3)::Maybe Int にラムダ関数をバインドさせるときは x が 3 がくると考えてよい。

(考えてよいというのは Monad の定義や Monad 則では何が来るのかは規定されていないため。もし別の x が来たとしても、 Just x == Just 3 となるはずなので問題ない。そういう意味で Monad は箱の中に入っているものを取り出せる。)

一方

Prelude> Nothing >>= \x -> return (3*x)
Nothing
ラムダ関数の x になにが来るのかは Maybe Int の実装に依存する。

 

同様に

Prelude> [2] >>= \x->return(3*x)
[6]

Monad 則より x に 2 が来ると考えてよいが

Prelude> [2,3,4] >>= \x->return(3*x)
[6,9,12]

では x に何がくるのかは [ ] クラスの >>= 実装に依存する。

(見て明らかなように [ ] の各成分がわたってきているわけだけど。)

 

IO の場合も同じように a -> m b を m a -> m b に移すと考えられる。

IO  Char -------> IO String

     ↑                        ↑

  Char ------------> String

Prelude> getChar >>= \x -> return $ foldl (++) "" $ replicate 3 (show x)
e
"'e''e''e'"

Prelude> return 'e' >>= (\x -> return $ foldl (++) "" $ replicate 3 (show x))
"'e''e''e'"

 

getChar で取得した 'e' ::IO Char にせよ return で取得した 'e' ::IO Char にせよ、

IO Char 上では同一なので、ラムダ関数の x の実装がそのまま同様に適用される。