人気ブログランキング | 話題のタグを見る

Yet Another Haskell Tutorial 29

9.4 Common Monads

このセクションではよく使われる2つのモナド、List モナドと Maybe モナドについて説明されている。リストやMaybe 型のデータはよく使われるが、これらは実はモナドでもある。

リストモナドの定義は次のようになっている。

instance Monad [] where
  return x = [x]
  l >>= concatMap f l
  fail _ = []

YAHT ではリストモナドの関数の定義についてはさらっと触れるだけで、応用の説明に移っているが、ここでは定義について少し考えてみる。

return 関数は引数をひとつとり、それをリストにラッピングして返す。たとえば return 1 == [1] だ。return は多相関数なので IO モナドでも同じ名前の関数が使われる。ghci で単に return 1 とすると IO 1 が返るが。

Prelude> return 1
1

次のように工夫すれば、型推定を利用して、return 1 が [1] であることを確かめることができる。

Prelude> return 1 ++ [2,3]
[1,2,3]

次の定義は、bind 演算子 >>= の定義だが、最初の引数の l はリスト型のデータで、次の引数の f は引数がひとつで戻値がリストの関数 a -> [b] 型の関数であることに注意しておく必要がある。

  l >>= concatMap f l

上の定義で concatMap f l は、concat $ map f l だ。つまり、ます、リスト l の要素の各々に関数 f を関数適用させたリストを作る。要素は a 型で、f :: a -> [b] は a 型の引数ひとつを取り、リスト型の値 [b] を返す関数だ。したがって、map f [a1,a2,a3] == [[b1], [b2], [b3]] となる。これに concat を関数適用すると、[b1,b2,b3] になる。これは ghci で次のように確かめることができる。

Prelude> [1,2,3] >>= (\x -> [x*2])
[2,4,6]

fail 関数はどのような引数に対しても空のリスト [] を返すが、後の例には出てこないので省略する。

リストはモナドなので、次のように do 記法を利用することができる。

Prelude> :set +m
Prelude> let
Prelude| cross l1 l2 = do
Prelude|   x <- l1
Prelude|   y <- l2
Prelude|   return (x,y)
Prelude|

これはどういうプログラムなのだろうか、とりあえず使ってみて動作を見てみる。

Prelude> cross "ab" "cde"
[('a','c'),('a','d'),('a','e'),('b','c'),('b','d'),('b','e')]

結果はリスト "ab" の要素とリスト "cde" の要素をひとつづつ組み合せたペアからなるリストになる。しかし、どうしてこのような値になるのだろうか。

これを解読するためには、上の do 記法をモナドの演算に書き換える必要がある。それは次のようになる。

Prelude> let cross2 l1 l2 = l1 >>= \x -> (l2 >>= \y -> return (x,y))
Prelude|
Prelude> cross2 "ab" "cde"
[('a','c'),('a','d'),('a','e'),('b','c'),('b','d'),('b','e')]

これでも分かりやすいとは言えないので >>= 演算子を定義のとおりに書き換えてみる。

Prelude> let cross3 l1 l2 = concatMap (\x -> (concatMap (\y -> [(x,y)]) l2)) l1
Prelude|
Prelude> cross3 "ab" "cde"
[('a','c'),('a','d'),('a','e'),('b','c'),('b','d'),('b','e')]

これなら、リスト "ab" の 'a' を x にバインドして、x = a にたいしてリスト "cde" の要素とのペアのリスト [('a','c'),('a', 'd'),('a','e)]作り、 さらに 'b' とのペアのリスト [('b','c'),('b','d'),('b','e')] を作り、それらを結合させるという動作を読み取ることができる。

上の do 記法が次のようなリストの内包的定義と似ているのは偶然ではない。

Prelude> [(x,y) | x <- "ab", y <- "cde"]
[('a','c'),('a','d'),('a','e'),('b','c'),('b','d'),('b','e')]

実はリストの内包的定義は、リストモナドの do 記法の省略形なのだ。

Yet Another Haskell Tutorial 30 へ続く ...
by tnomura9 | 2013-03-14 00:30 | Haskell | Comments(0)
<< Yet Another Has... Yet Another Has... >>