モナドと実行順序のこと
kazu-yamamotoさんのコメントいただいて、モナドの実行順序関連の振る舞いについて考えてみました。
bind (>>=) が、右辺を実行する前に左辺を実行し終えるかについて。
リストもモナドなことは前回にも述べましたが、リストのモナドインスタンスの実装は以下のようになっています。
instance Monad where
m >>= f = concatMap f m
return x = [x]
fail s =
リストモナドのbindでは、concatMapを使っているので、右辺の実行は左辺の実行終了を待たないことになります。リストのモナド性をつかうと、結構面白いことができて、
[1, 2, 3] >>= (replicate 2)
[1,1,2,2,3,3]
なんて事が起きたりします。それはさておき、無限リストを利用して、bindを試してみました。
[1..] >>= (filter (< 5)).(:)
[1,2,3,4
これは、無限リストを上記の右辺にbindしてみたのですが、見てのとおり、1から4までは出力されて、そのあと、GHCiはハングしてしまいました。これは、すなわち左辺の実行とが終了する前に右辺が実行されている証明といえそうです。
ついでに、もう一歩進めて、
[1..] >>=(filter (< 5)).(:) >>= return.(+1)
[2,3,4,5
これで、複数のbindのあいだを生成中の無限リストが通り抜けていることがわかりますね...
次に調べたのがIOモナドなんですが、IOモナドのbindはプラットフォーム依存、つまりランタイムの中にコードがあるらしく、その振る舞いはHaskellの言語定義のみによって決定されるわけではなさそうです。 それでいてもHaskellの言語内ではIOはモナドとして定義されているので、モナド則を破綻させない程度の振る舞いは確保しなくてはいけないのだと思います。
試しにこんなコードを書いてみました:
module Main where ch :: IO Char ch = return 'a' chars :: IO String chars = sequence $ repeat ch main :: IO() main = chars >>= ( (mapM_ (putChar)).(take 5))
このコードは、何も表示することなくハングします。
[ここからは、間違った考察が書かれています。追記を参照してください。]
charsの結果が完全に生成されるまでbindの右側に渡らないということだと思います。ちなみに"repeat ch" を "replicate 7 ch"とかにかえるとちゃんと"aaaaa"と表示されます。このコードでは、chもcharsも実際のIOは行っていないにもかかわらずこうゆう動きになります。おそらくIOのbindが左辺の終了を待つように書かれているのだろうと思います...
[/誤りの考察終了]
よくよく考えてみれば、そうしないと左辺の両方からputCharなど副作用のあるコードが動いた際の副作用の実行順序が保証されませんよね...
[追記]上のコードがハングする理由は>>=::IOのせいではなく、どうやらcharsで使われているsequenceによる理由であることが判明しました。更なる追求の結果をポストしました。(汗)
いやー、いろいろ勉強になりました。
資料1:http://www.haskell.org/all_about_monads/html/listmonad.html
資料2:http://www.haskell.org/all_about_monads/html/iomonad.html