みどりねこ日記

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

Haskellのモナド2(2)

Maybeモナドの計算(Maybeモナドで包まれたものが結果となる関数呼び出し)は、「失敗するかもしれない計算」を表現しています。これの良い例として、データベースのルックアップテーブルから条件にマッチするものを引っ張ってくる処理があります。ルックアップテーブルは値とキーが結びついたテーブルのことです。例えば、名前と電話番号が結びついたルックアップテーブル、「電話帳」を考えてみましょう。Haskellでキーと値が結びついているテーブルを表す一つの方法として、(a, b)というタプルのリストを使うという手があります。ここでは、aをキーの型、bを値の型とします。電話帳はこのようなルックアップテーブルとなります。

phonebook :: [(String, String)]
phonebook = [
	("Bob", "0132832")
	,("Alice", "3829392")
	,("Cecil", "8934211")
	,("David", "4389318")]

ルックアップテーブルで良くすることと言ったら、値を探すことですよね。しかし、この計算は失敗する可能性を持っています。もし、Bob, Alice, Cecil, Davidを検索するのならなんの問題もなく動作するでしょう。しかし、Jessieを検索するとどうなるでしょう?Jessieはこの電話帳に登録されていないので失敗します。このため、Haskellではテーブルから値を探す関数はMaybeモナドの計算となります。

lookup :: Eq a => a	-- キー
	-> [(a, b)]	-- 検索元テーブル
	-> Maybe b	-- 結果

この関数を使ってみます。

> lookup "Alice" phonebook
Just "0132832"
> lookup "Cecil" phonebook
Just "8934211"
> lookup "Jessie" phonebook
Nothing

さて、このコードをモナドのちからをフルに使って拡張してみましょう。そうですね、今私たちは政府のために働いていて、与えられた名前を大規模なサイズのphonebookから検索し、その電話番号を取得して、その電話番号を使って別のデータベースから車の登録番号を取得したいとします。この処理はもちろん別のMaybeな計算となります。しかし、もしphonebookに登録されていないのなら、当然車の登録番号は取得できませんよね。ですので、最初の計算結果を受け取って、それがNothingでないなら2つ目のlookupを実行する、という関数が必要になります。もし最初の計算の結果がNothingなら、または2つ目の計算結果がNothingなら、最終的な計算結果もNothingとなるべきです。

comb :: Maybe a -> (a -> Maybe b) -> Maybe b
comb Nothing _ = Nothing
comb (Just x) f = f x

もうわかったかもしれませんが、comb関数はMaybeモナド計算にとっての>>=なのです。なので、これを使って計算をつなげることが出来ます。

getRegistrationNumber :: String -- 名前
	-> Maybe String -- 登録番号
getRegistrationNumber name =
	lookup name phonebook >>=
		(\number -> lookup number gobernmentalDatabase)

もし3つ目の処理をこのあとしたいとしたら(ここでは登録番号からどれだけの税金を払う必要があるかが取れるデータベースがあるとします)、getRegistrationNumberを以下のように拡張することが出来ます。

getTaxOwed :: String -- 名前
	-> Maybe Double
getTaxOwed name =
	lookup name phonebook >>=
		(\number -> lookup number governmentalDatabase) >>=
			(\registration -> lookup registration taxDatabase)

またはdo記法を使って、

getTaxOwed name = do
	number <- lookup name phonebook
	registration <- lookup number governmentalDatabase
	lookup registration taxDatabase

さて、ここでもしどこかの処理でNothingが返ってきた時どうなるか考えてみましょう。>>=を使ってNothingを次の計算に連結した場合、結果はそのままNothingとなり2つ目の計算は実行されません(どうして?と思ったら、上のcombの定義を見て下さい)。つまるところ、どの段階でNothingが返ってきたとしても、他の関数に関係なく、計算結果はNothingとなります。このように、Maybeモナドの構造は失敗を伝えていくといえます。

このことはlookupに限ったことではないことに注意してください。失敗するかもしれないためにMaybeを使う関数はたくさんあります。おそらくあなた自身も1度か2度は書かれたことがあると思います。すべてのMaybe内の計算はこのように結合することが出来ます。


まとめ:
1. Maybeモナドは失敗するかもしれない計算を表している
2. Maybeモナドは失敗を伝えていく