人気ブログランキング | 話題のタグを見る

複数の引数を持つIOモナド型関数

これまでの記事で、IOモナド型関数を、「引数をひとつ取りIO型の戻値を返す関数」と単純化することによって、自前のIOモナド型関数を、他のIOモナド型関数と >>= (bind 演算子)でつなげて使うことができるのがわかった。A >>= B のように、関数Aが関数Bとbind演算子で結合されているとき、関数Aの戻値 IO b が関数Bに渡されるが、IO b が直接にBに渡されるのではなく、コンテナIOから裸の b がbind演算子によって取り出されて、Bには b が渡されることになる。

ここで、IOモナド型関数の戻値は必ずIO型でなくてはならないが、戻値 b がIO型のコンテナにくるまれていなかったとしたら、A >>= B の動作は単なる関数の合成になる。つまり、関数Aと関数Bを合成するのに>>=演算子を使わなければならないのは、関数Aの出力が必ずIO型になるからだ。次段の関数Bは引数をIO型としては受け取ることができないため、必ず>>=演算子を介して引数を受け取ることになる。

このちょっとした工夫のために、一般の関数をIOモナドと>>=演算子で結合しようとすると型エラーが生じて結合することができない。このため、一般の関数はIOモナドの戻値を受け取ることができず、副作用のあるIOモナド型関数の戻値を利用できない。つまり、IOモナドの関数は、他の関数から隔離されることになる。

このように、IOモナド型関数が>>=演算子で結合される限り、IOモナド型関数の動作は他の動作から隔離される。また、>>=演算子による演算では前段の戻値がないと動作ができないので、関数型言語でありながら、関数Aと関数Bの実行順序を変えることができない。このため、>>=演算子で結合された関数A とBは実行順序が変わらないため、IOモナド型関数は>>=で結合されている限り、手続き型の言語と同じような実行順序が保証される。

IOモナド型関数の「引数をひとつ取り、IO型の戻値を返す」という性質は>>=演算子によって結合させるための条件なのだ。IOモナド型関数が全てdo記法の中に記述されるのは、それらがすべて実際には>>=演算子で結合されているという意味がある。do記法は>>=演算子で結合されるIOモナド型関数が手続き型のプログラムのように見せかけるためのシンタックスシュガーなのだ。

ところが、標準のIOモナド型関数の中には、writeFile "fname" "contents" のように引数を二つもつものがある。これは今の議論から考えると不都合が生じるように思えるが、この問題は関数のカリー化によって解決される。つまり、getLIne >>= writeFile の結合では引数の数が合わずエラーになるが、getLine >>= writeFile "myfile.txt" の場合はカリー化された関数 writeFile "myfile" はString型の引数をひとつ取るため、IOモナド型関数の引数をひとつ取りIO型の引数を返すという要件を満たすことになる。

したがって、自前のIOモナド型関数も複数の引数を持たせることが可能で、次のような関数 reply も>>=演算子を使って他のIOモナド型関数と>>=で結合できる。

reply :: String -> String -> IO String
reply greeting name = return (greeting ++ name)

実行例

Main> getLine >>= reply "hello, " >>= putStr
Dolly
hello, Dolly

このように、IOモナド型関数は「引数をひとつ取りIO型の戻値を返す。」と単純化して考えることによって、圏論のモナドの理論や、副作用の隔離や世界の状態の変化や、実行順序の保存などの理論的な難しさを回避して、IOモナドを活用することができる。
by tnomura9 | 2010-10-15 07:16 | Haskell | Comments(0)
<< メタ思考 IOモナド怖い、まんじゅう怖い。 >>