Haskell 入門


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"

のようにしても良い。