モナド
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はモナドを使って副作用を実現していることが理解出来ます。
「現実世界」をバケツリレー的に左辺から右辺へどんどん渡していく感じですね、副作用を与えながら。
とりあえずこの理解で進んでみよう。
違ってたら立ち戻って考え直そう。