IOモナドの再帰関数の注意点

Haskell のループについて色々と調べてみたが、結局再帰関数を利用するのが一番オーソドックスだった。しかし、IO モナドの再帰関数を定義するときは少し工夫がいる。

次の例は再帰関数の代表例で、配列の長さを調べる関数だ。

lsLen xs =
  if null xs
    then
      0
    else
      1 + lsLen (tail xs)

最後の行が再帰的定義の部分で、lsLen 関数の中で lsLen 関数を呼び出している。関数の定義を同じ lsLen で定義しているので再帰関数だ。

同じような再帰的定義を IO モナドで行ったのが次の getLines だ。この関数はコンソールからの入力を次々に読取って、空行が入力された時、今までの行のリストを戻値にして IO [String] の形で返す。

getLines = do
  x <- getLine
  if x == ""
    then
      return []
    else do
      xs <- getLines
      return (x : xs)

最初のものとそっくりだが、最後の行が do 記法の中で行われ、xs <- getLines のように getLines の戻値が <- 演算子で xs に束縛されている所が違う。それは、getLines の戻値が IO [String] のようにIOモナド型なので、 x : getLines のような直接的な結合ができないためだ。

これを行うためには、xs <- getLines のように一担 IO [String] 型のコンテナから、[String] 型のデータを取り出して x : xs とし、それを再び return 関数で IO [String] 型にして戻値にしなければならない。

面倒な操作が一段階入るのだが、逆に言うと、この点にだけ注意しておけば、IO モナドの中でも再帰関数が自由に書けるということだ。

Haskell のループの基本は再帰関数だ。それは IO モナドでも変わらず適用できるようだ。
[PR]
by tnomura9 | 2011-11-01 11:33 | Haskell | Comments(0)
<< IOモナドの中の let の中... Haskell のインデント >>