勉強会に行ったり、ブログや本を読んだりいろいろしたけど、モナドってなんなんすかね。(´・ω・`)
状態だった僕ですが、分解して、図解してみたら少しわかった気がするので、エントリーしてみる。
「プログラミングHaskell」のP106によると、IOモナドとは、以下の形をしているらしい*1。
type IO a = World -> (a, World)
そして、次のページにバインド(>>=)の実装が以下のように書かれている。
(>>=) :: IO a -> (a -> IO b) -> IO b f >>= g = \ world -> case f world of (v, world') -> g v world'
はい。一気にギアが上がりましたね。(´・ω・`)
全然分かんない。(´・ω・`)
ということで、これを分解して図解するのがこのエントリーの趣旨です。
IO a について
まず、この書式だが。
(>>=) :: IO a -> (a -> IO b) -> IO b
ここでいう「IO a」というのは、「World -> (a, World)」のことだったので、下記のようにあらわしてみる。
Worldをとって、a(任意の型)とWorldの組を返す関数で、その中身はIO a実装側次第のブラックボックスということになる。
回路図のようなイメージにすると、こんな感じ?
バインドの定義
これを利用して、バインドの定義を補足すると、こんな感じになる。はず。
つまり、バインド(>>=)を完全に展開すると、「w -> (a,w) -> a -> w -> (b,w) -> w -> (b,w)」ということになるってことだと思う。
これをバインド(>>=)の実装に対応させるとこんな感じ。gは、IO bを含む関数なのでもう一段展開させておく。
なんかちよっとごちゃごちゃしてきたけど、気にしないで先に進んでみる。(´・ω・`)
そしてついにバインド(>>=)の実装を分解する
バインド(>>=)実装をよく読むと、ラムダ式が使われていて、その書式が「world -> (b, world')」になっているので、展開された「IO b」と一致している。
その辺のつながりがわかりやすいように線で結ぶとこんな感じになる。
全開でごちゃごちゃして解りにくいのだが。(´・ω・`)
よく見ると、赤い線は簡単だ。返却値となる「IO b」の引数のWorldが、呼び出し順を逆に巡って言っている。
この間に、他の関数の関与の余地がないため、このWorldは状態が保存されたまま、ひたすらモナドの流れを逆流することになる。
そして、緑の線は難解なトグロを巻いているが、バインド(>>=)の引数側の「IO a」の返却値(a,World)をユーザー定義の関数であるところの「g = (a -> IO b)」に引き渡し、その返却値がバインド(>>=)の返却値となっている。この返却値「IO b」は、次のバインド(>>=)の引数となる。
緑の線は、バインド(>>=)連鎖の最深部から、ユーザー定義のgによって状態を変えながら、地上に向かってトグロを巻きながら進んでいくことになる。
バインド(>>=)は、それ自体で完結していない。連鎖することによって、意味を為す構造をしているため、これだけを見ていてもすべてを理解したことにならない。
最後の図を示そう。
これがぼくのかんがえたさいきょうのばいんどだ!(`・ω・´)
とかは置いといて、さっきのバインド(>>=)を三つつないでreturnとmainにつないでみた。
「return 1 >>= print >>= print >>= print」的なことだと思ってほしい*2。
IOモナドは、mainとつながりmainに返却した「IO b(図中ではIO d)」の引数のworldから、物語が始まる。
このworldは、状態を保存したまま、バインドの連鎖を駆け上がる。そして、returnにぶつかると折り返して、今度は、緑のうねりをたどりながら状態を変化させ、最後にmainの返却値の「IO b」が展開された関数「world -> (a, world)」の返却値として外の世界へ帰っていく。
なんとも、ロマンのある話ではないか。
Worldを現実世界とするならば、IOモナドは、現実世界から引きまわされたパイプのようなものだと理解した。
そして、現実世界とコンタクトするチャンスは、唯一「関数g」のみとなる。関数gからこちら側は、純粋で透明な関数の世界になっている。世界がどんなに汚れていても、それは引数に渡される値の多様性として現れる。
実に見事で、美しい仕組みなんだな。(´・ω・`)
どうやったら、こんなこと思いつけるんだろう。(´・ω・`)