みどりねこ日記

よくわからないけど、頑張りますよ。

Haskellのモナド(4)

Maybeモナドにおける>>=演算子の便利さを知るために、以下の2つの関数を提供する家系のデータベースがあるとします。

father :: Person -> Maybe Person
mother :: Person -> Maybe Person

これらは、誰かの父親・母親の名前を返すか、データベース中に存在しない場合にNothingを返してくる関数です。これらを組み合わせることで、いろいろな先祖を遡っていくことが出来ます。例として、母方の祖父を取ってくるには、以下のような関数で可能となります。

maternalGrandfather :: Person -> Maybe Person
maternalGrandfather p =
	case mother p of
		Nothing -> Nothing
		Just m -> father m

父方、母方のどちらの祖父もデータベースにあるかをチェックする関数を考えてみます。

bothGrandfathers :: Person -> Maybe (Person, Person)
bothGrandfathers p = case father p of
	Nothing -> Nothing
	Just f ->
		case father f of
			Nothing -> Nothing
			Just gf ->
				case mother p of
					Nothing -> Nothing
					Just m ->
						case father m of
							Nothing -> Nothing
							Just gm ->
								Just (gf, gm)

非常に見にくいですね。関数で問い合わせるたびに、もしかしたら失敗するかもしれないということを考える必要があります。

しかし、明らかに、caseを繰り返し書いていちいちNothingかどうかをチェックするより良い方法がありそうですね。当然です。Maybeモナドはそのためにあるのですから。例えば、先ほどの母方の祖父を調べる関数は、>>=演算子の定義と全く同じ形をしていますね。書きなおしてみましょう。

maternalGrandfather p = mother p >>= father

ラムダ式とreturnを使うことで、どちらの祖父も存在するかをチェックする関数も簡単に書くことができます。

bothGrandfathers p = father p >>=
	(¥f -> father f >>=
		(¥gf -> mother p >>=
			(¥m -> father m >>=
				(¥gm -> return (gf, gm))))

ネストしたラムダ式がごちゃごちゃして見えるかもしれませんが、ここで嬉しいのは、コード中に一切Nothingが出てこず、コードの核心部分にのみ集中できることです。

さて、Haskellは上のような書き方をもっと良い物にした糖衣構文を用意しており、あたかも命令型言語で書かれたかのようにみえます。この構文はただ便利であるというだけで、実際に処理されることは変わりませんが、使い勝手のいい美しいものです。詳細については後述しますが、上のコードをそのdo構文で書きなおしてみたものを示しておきます。

bothGrandfathers p = do
	f <- father p
	gf <- father f
	m <- mother p
	gm <- father m
	return (gf, gm)