Yet Another Haskell Tutorial 22

9.1 Do Notation

このセクションでは IO モナドの do 記法が、Monad (タイプ) クラスの関数によるオペレーションの合成を、手続き型のプログラムに見せかけるためのシンタックス・シュガーである事を示そうとしている。そうして、do 記法をモナドの演算に翻訳してみせる。

IO モナドを Monad (タイプ)クラスの関数で動かすということはどういう事かというと、getLine や putStrLn などのオペレーション(アクション)を >>= 演算子で、シークエンシャルに繋いでいくことだ。たとえば、getStrLn と putStrLn は >>= 演算子で次のように繋ぐ事ができる。

Prelude> getLine >>= \cs -> putStrLn cs
hello, world
hello, world

このプログラムと do 記法を使った次のプログラムは本質的には同じだ。

Prelude> :set +m
Prelude> do
Prelude|   cs <- getLine
Prelude|   putStrLn cs
Prelude|
hello, world
hello, world

do 記法のプログラムを Monad の演算の形式に翻訳することの利点は、do 記法で記述されたプログラムを手続き型言語のアナロジーで作ったときの不可解なエラーを防ぐ事ができるという事だ。

YAHT の説明は厳密に書かれているので、少々ポイントを掴むのが難しい。このブログの「IO モナドとの付き合い方」という記事にその辺りの事情を書いているので、先に読んでもらった方が分かりやすいかもしれない。書いた本人がそう思っているだけかもしれないが。

前置きはこれくらいにして、次からは YAHT の記事にそって丁寧に読んでいく事にする。

YAHT の 9.1 Do Notation のセクションでは、モナドの do 記法が4つの変換規則で Monad の演算の形式に書き換える事ができる事を示してある。最初に次の例示プログラムが、

main = do
  s <- readFile "somefile"
  putStrLn (show (f s))

次のようなモナドの演算形式に変換できる事を示してある。

main =
  readFile "somefile" >>= \s ->
  putStrLn (show (f s))

これは、厳密な説明ではない。なぜなら、このセクションでは Monad (タイプ)クラスの関数が次のような型である事を示してあるが、IO モナドにおける各関数の実装は説明してないからだ。

class Monad m where
  return :: a -> m a
  fail :: String -> m a
  (>>=) :: m a -> (a -> m b) -> m b
  (>>) :: m a -> m b -> m b

Class キーワードを使ったタイプクラスの宣言では、そのタイプクラスの多相関数の型を示すだけである。各多相関数の実装は instance 宣言で行うが、このセクションでは IO モナドのインスタンス宣言については述べられていない。

したがって、上に述べた4つの法則も、論理的に説明してあるのではなく、このようにすれば do 記法をモナド演算に翻訳できるという手続き的な説明であると考えないといけない。

ともあれ、上に示された do 記法からモナド演算への変換についてもう少し考えてみよう。

このような簡単なプログラムの場合、複数行のプログラムで考えるより、一行プログラムで考えた方が分かりやすい。また、上のプログラムでは、ReadFile アクションで読み出すファイルを作ったり、関数 f を定義したりしないと ghci で実験できないので、例示プログラムを次のように変えてみた。

do x <- getLine; putStrln x

これは次のように ghci で簡単に実験できる。

Prelude> do x <- getLine; putStrLn x
hello, world
hello, world

YAHT 本文のやり方に従うと上のプログラムは次のようにモナド演算の形式に変更できる。

getLine >>= ¥x -> putStrLn x

これも次のように ghci で動かす事ができる。

Prelude> getLine >>= \x -> putStrLn x
hello, world
hello, world

この変換の意味を考えるために (>>=) の型について考えてみよう。上に述べた class 宣言を見ると、(>>=) 多相関数の型は次のようになる。

(>>=) :: m a -> (a -> m b) -> m b

これは、(>>=) の第1引数がモナド型の m a で、第2引数が a 型を引数としモナド型 m b の戻り値を返す関数で、(>>=) の値がモナド型 m b であることを示している。

中値演算子の形式で書くと次のようになる。

モナド型 >>= (a -> モナド型)-> モナド型

上の型宣言と getLine >>= ¥x -> putStrLn x を比較すると、これが (>>=) の型宣言を満たしている事が分かるだろう。つまり、getLine の型は次のように IO String 型であるし、

Prelude> :t getLine
getLine :: IO String

¥x -> putStrLn x は次のように String -> IO () 型の関数だからだ。

Prelude> :t \x -> putStrLn x
\x -> putStrLn x :: String -> IO ()

さらに、getLine >>= ¥x -> putStrLn :: IO () の型は次のように IO () 型になる。

Prelude> :t getLine >>= \x -> putStrLn x
getLine >>= \x -> putStrLn x :: IO ()

結局 YAHT でしめしてある変換ルールの

do {x <- f; g x} ===> f >>= ¥x -> g x

は、do 記法が、IO モナド型の f と (a -> IO モナド型) の関数 ¥x -> putStrLn x を >>= 演算子で合成すると IO モナド型の値になるというモナド演算に変換できる事が分かる。

IO モナド型と (¥x -> g x) 型の関数の >>= による合成の値が IO モナド型のデータであるという事は、

f >>= (¥x -> g x) >>= (¥x -> h x) >>= (¥x -> i x)

というように (¥x -> g x) 型の関数を次々につなげていくことができるという事を意味している。IO モナド型の関数に関わらず Monad (タイプ)クラスに属するデータ型については同じように次々と関数の結合を増やしていく事ができる。

このあと YAHT では do 記法をモナド演算に変換するための4つのルールについて解説してあるが、長くなるので次の記事で述べる。

Yet Another Haskell Tutorial 23 へ続く ...
[PR]
by tnomura9 | 2013-03-05 00:23 | Haskell | Comments(0)
<< Yet Another Has... Yet Another Has... >>