IOモナド使い方のこつ
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 モナドでも変わらない。
IOモナドの中の let の中はIOモナド世界の中の Haskell 空間
モナドの do 記法の中には原則としてモナド型関数しか使えない。しかし、 do 記法の中の let の中では Haskell のプログラムが普通に組める。
次のプログラムでは、do 記法の中の let 空間に Haskell のプログラムを作って、それをIOモナドの中で使っている。
main = do let fact 0 = 1 fact n = n * fact (n - 1) print (fact 5) let hello = "hello, world" putStrLn hello let num x = case x of 1 -> "one" 2 -> "two" _ -> "others" putStrLn (num 2)
実行例
*Main> main 120 hello, world two
do 記法の中で let を使うと、let 空間の中では純粋関数の Haskell のプログラムを自由に書くことができる。
上の例では let の中に一つだけの関数を置いたが、複数の関数をまとめて、
let fact 0 = 1 fact n = n * fact (n - 1) hello = "hello, world" num x = case x of 1 -> "one" 2 -> "two" _ -> "others"
のようにしても良い。