IOモナドのループ処理

IOモナドのプログラムミングは、基本的にはIOモナドの関数「引数一つ、戻値IO型」の関数を、>>= 演算子でつないでパイプラインを構築することだ。しかし、手続き型になれた身からしたら、for ループがないのはいかにも心もとない。

Haskell の解説書には、「Haskell には for 文はない、全て再帰関数で記述する。」などと説明してある。例えば*を10個表示するプログラムでは、手続き型では、

for i in 1..10
  print "*"
end

のようにするが、Haskell では、

Main> putStrLn $ replicate 10 '*'
**********

で済ませてしまうので、ループ処理は現れてこない。しかし、すべてをループを使わないというのも無理があるので、末尾再帰を使って for や while や do ~ until を記述してみた。

末尾再帰というと難しそうに聞こえるが、要するに、for i n と for 関数の引数にインデックス i と終了条件 n をとり、ループするときは、インデックスをひとつカウントアップした (i+1) と終了条件はそのままの n を引数にして for (i+1) n を呼ぶだけだ。

また、i > n のように終了条件が満たされたら、return () で、IO () などのようなIO型の戻値を渡してループを終了する。IOモナドの関数(アクション)は、必ずIO型の戻値を返さなければならないので、ループの処理の最後に必ずIO型のデータを戻値として返さなければならない。手続き型からの発想だとそれを見落として、型エラーを発生させてしまうので注意が必要だ。

これらの3つのループ処理は基本的には同じ動作なので、下の例のように、for も while も until も似たようなコードになる。

forTest = for 1 4
  where
    for i n = if i < n
      then do
        print i
        for (i+1) n
      else
        return ()


whileTest = while 1 4
  where
    while i n = if i < n
      then do
        print i
        while (i+1) n
      else
        return ()


untilTest = until 1 4
  where
    until i n = do
      print i
      if i < n
        then until (i+1) n
        else return ()

注: Haskell のインデントはオフサイドルールで意味がある場合があるので、難しく感じるが、オフサイドルールは、where, let, do, of というキーワードの後のブロックの{}と;を省略するときのルールなので、このキーワードの後方だけ注意しておけば、あとの改行と空白は単に無視されるだけなので普通にインデントできる。

実行例は次のようになる。

Main> forTest
1
2
3

しかし、これくらいのことなら次のように1行で足りる。

Main> mapM print [1,2,3]
1
2
3

IOモナドに手続き型のようなループ処理を導入するのは難しくはないが、発想の方を関数型に変えたほうがずっと楽だ。


今日のHaskell
Hugs> mapM putStrLn ["What","a","beautiful","day!"]
What
a
beautiful
day!

mapM は、map 関数のIOモナド版
[PR]
by tnomura9 | 2010-11-06 13:45 | Haskell | Comments(0)
<< IOモナドの多重ループ処理 IOモナドとHaskell の... >>