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

>>= 演算子と do 記法

IOモナド型関数とは、引数をひとつ取り、IO型の戻り値を戻す関数のことだ。また、>>=演算子は、IOモナド型関数とIOモナド型関数を結合させる演算子で、左辺の関数のIO型の戻り値のパラメータを取り出し、右辺の関数の引数として渡す働きがある。

従って、次のようなプログラムでは、getLine 関数の戻り値のパラメータである文字列が、putStr 関数の引数として渡され、その文字列が表示される。

Hugs> getLine >>= putStr
hello, world
hello, world

ところが、このプログラムは、do 記法を使うと次のように記述することができる。

Hugs> do { cs <- getLine; putStr cs }
hello, world
hello, world

これは、ファイルにプログラムを作成して実行させるときはインデントを使って次のように書ける。

main = do
  cs <- getLine
  putStr cs

最初の >>= 演算子を使った記法に比べると、手続き型のプログラムによく似ている。しかし、do 記法で書かれていても、これもやはり、IOモナド型関数を>>=演算子で結合したものだ。do 記法はあくまでもIOモナド型関数を >>= 演算子でつなぐ操作の、シンタックスシュガーだ。

cs <- getLine の意味は、getLine の戻り値のIO型のデータからパラメータを抜き出し、cs という関数名に束縛する働きがある。cs は束縛された値を常に返す関数なのだ。したがって、cs <- cs ++ cs のように cs の値を後で変更することはできない。また、cs <- "hello, " ++ "world" のような使い方はできない。演算子の右側の関数の戻り値がIO型ではないので型エラーが起きてしまう。そのような動作を期待する場合は、cs <- return ("hello, " ++ "world") のように、return 関数を使ってIO型のパラメータとしてIO型のデータのコンテナに入れてやらなければならない。

Hugs> do { cs <- return ("hello, " ++ "world"); putStr cs }
hello, world

do 記法が、手続き型言語の記述法に似ていると言っても、やはり、これも関数を>>=演算子でつないだ、関数型のプログラムであって、手続き型のプログラムではない。do 記法の中で手続き型のプログラムを作ろうとしてもうまくいかないだろう。

副作用のある入出力のプログラムを記述するときも、極力、関数はdo 記法の外側で記述して、do 記法の中に記述するプログラムはできるだけ少なくする必要がある。

ただ、do 記法の中に記述できる関数はIOモナド型関数でないといけないので、どうやって自前のIOモナド型関数を作るのかという問題になるが、IOモナド型関数の特徴である引数をひとつとり、IO型の戻り値を戻すという性質をおさえておけば、関数の戻り値を return 関数によってIO型にくるんでやれば良いことがわかる。

次のプログラムは、コンソールから入力した文字列を2つ並べて表示するプログラムだが、dup 関数が自前のIOモナド型関数である。文字列をひとつ取り、処理した文字列を return 関数でIO型にくるんでいるだけだ。自前のIOモナド型関数と言っても、作るのは簡単だということがわかる。

Hugs> do { cs <- getLine; ds <- dup cs; putStr ds } where dup as = return (as ++ as)
hello,
hello,hello,

もちろん、このプログラムは >>= 演算子を使っても、次のように実行することができる。

Hugs> getLine >>= dup >>= putStr where dup cs = return (cs ++ cs)
hello,
hello,hello,

do 記法を使うと、擬似的に手続き型のプログラムを記述できるように見えるが、実態は、これも全くの関数型のプログラムだったのだ。

手続き型のプログラムのテクニックを、そのまま、Haskell に持ってくることはできない。Haskell でプログラムを組むときは、あくまでも、関数を記述するという視点が必要だ。しかし、それは、とてつもなく難しいことではなく、単に慣れの問題に過ぎず、習得するのにそれほど大きな壁はないのではないかという気がする。
by tnomura9 | 2010-10-13 07:34 | Haskell | Comments(0)
<< WinHugs のプロンプトで... IOモナドの正体 >>