モナド

11.4 IOモナド


う〜ん、昨日は一日中(というと大げさですが)考えてしまいました。
IOモナド
何だかよく分かりません。
ここのIO型の定義を見ても…

instance Monad IO where
   (>>=)  = ...
   return = ...
   fail s = ioError (userError s)


「...」って何だ?
ここによると

省略記号 "..." は多くの場合、残りの定義部分 が Haskell では与えられないところで使用している。


とのこと。これだけでは理解出来ないことが確定しました。


ということで無理やり自分自身で理解してみました。
とりあえずツジツマは合ってるので、これで進めてみようかな。

分からないなりにまとめてみる

まずIOとモナド
この二つは別もの。
モナドは(>>=)とreturn(と(>>))とで演算をつなぐことが出来るクラス。
IOはアクション。クラスや関数というよりも値に近いモノ


この二つを明確に切り分けて考えた方がすっきりしそうです。
モナドは上の理解で問題なさそう。
問題はIOの方。


本書では、アクションを次のように説明しています(本書の文章そのものではありませんが)。


「まだ入出力を実行していない現実世界」を中に含むアクション。
このアクションが評価されると「まだ入出力を実行していない現実世界」を元に、
「入出力結果」と「入出力を実行したあとの現実世界」を生成する。


始め読んだ時、ちんぷんかんぷんだったけど、今の理解はこんな感じ。
たとえばgetContentsというアクションは

Prelude> :i getContents
getContents :: IO String 	-- Defined in System.IO


という型です。まぁ値の型(たとえばIntegerとかCharとか)と同じノリですね。
これを上のアクションの説明とからめると…


IO … 「まだ入出力を実行していない現実世界」と「入出力を実行したあとの現実世界」(以降「現実世界」と記載)
String … 「入出力結果」


となります。
つまりアクションは「現実世界」と「入出力結果」を同時に表すモノなんだと。
この「現実世界」と「入出力結果」を同時に表すのが「IO String」という表現なわけです。
getContentsに入力されるのは「まだ入出力を実行していない現実世界」で、
これが評価されたら「入出力を実行したあとの現実世界」と「入出力結果」が得られます。


別の例を考えてみます。
putStr。定義はこんな感じ。

Prelude> :i putStr
putStr :: String -> IO () 	-- Defined in System.IO


putStrはgetContentsと違いアクションではなく、アクションを返す関数です。
戻り値のアクションであるIO ()とは、


IO … 「現実世界」
() … 「入出力結果」※意味のない値を示す型(Trivial type)


です。
つまり「入出力結果」はないけど、「現実世界」を変えてしまうアクションを意味します。
確かにputStrに「入出力結果」は不要ですよね。


さぁ、ここまででIOに関する理解が深まりました(これで間違っていなければ…ですけど)。
次はこのIOとモナドの関係です。


モナドはこれまで見てきたように(>>=)とreturnとで演算をつないでいきます。
これをIOクラスに当てはめてみると、例えば次の例。

Prelude> getContents >>= putStr
yyoohhppaappaa  ※"yohpapa"と入力した結果です。


getContentsアクションの型はIO String。
putStr関数の型はString -> IO ()。
単純に考えるとgetContentsアクションの型とputStr関数の引数の型は異なります。
昨日も書きましたが、この違いを吸収するのが(>>=)です。
もう一度(>>=)演算子の型を書きますと

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


つまり左辺のgetContentsアクション(型はIO String)から、「入出力結果」(型はString)を取り出し、
右辺のputStrの引数とするわけです。


ここでこんな疑問がわいてきます。
IO StringのうちのIO「現実世界」はどうなっちゃうの?


ここからは想像なんですけど、
多分(>>=)演算子はこのIOも右辺に引き渡すんじゃないでしょうか?
左辺(getContents)で入出力動作を実行されてしまった「現実世界」を右辺(putStr)に渡す。
右辺(putStr)は渡された「現実世界」を使って(もちろん「入出力結果」(String)も使って)、
IO ()、つまりアクションを生成するわけです。


こう考えると、IOはモナドを使って副作用を実現していることが理解出来ます。
「現実世界」をバケツリレー的に左辺から右辺へどんどん渡していく感じですね、副作用を与えながら。


とりあえずこの理解で進んでみよう。
違ってたら立ち戻って考え直そう。