※この記事のEither編はこちら
idと名前のデータベースと、順位とidのデータベースからなる、下記のようなフレームワークを考えてみよう。
2位の名前を取り出すには、idFromRank で 2位のidを取り出し、その id を nameFromId に渡して名前を得るものとする。
idFromRank も nameFromId も要求された順位やidのデータがなければ Nothing に評価されるものとする。
type ID = Int type Rank = Int type Name = String type RankDB = [(Rank,ID)] type NameDB = [(ID,Name)] idFromRank :: RankDB -> Rank -> Maybe ID idFromRank db rk = lookup rk db nameFromId :: NameDB -> ID -> Maybe Name nameFromId db id = lookup id db
このフレームワークを使って、上位3位の名前を取得する関数を作成してみよう。
ポイントは、idFromRank も nameFromId も Nothing に評価されることがあるため、この関数もMaybe型に評価されるものとし、1位から3位までのidがすべて存在し、それらの名前がすべて存在する場合のみJustに評価され、それ以外のときはNothingに評価されるようにすることだ。
下記のように呼び出されるようになればよいだろう。
main = do print $ topThree [(1,101),(2,102),(3,103)] [(101,"1st"),(102,"2nd"),(103,"3rd")] print $ topThree [(1,101),(4,102),(3,103)] [(101,"1st"),(102,"2nd"),(103,"3rd")] print $ topThree [(1,101),(2,102),(3,103)] [(101,"1st"),(104,"4th"),(103,"3rd")]
式を使ったプログラミングで素直に記述する場合、筆者の能力では下記の記述が限界である。
これはどう考えても地獄である。我々が関数プログラミングに求めていたものはこんなものではなかったはずだ。
topThree :: RankDB -> NameDB -> Maybe (Name,Name,Name) topThree rdb ndb = let maybeId1 = idFromRank rdb 1 in if isNothing maybeId1 then Nothing else let maybeName1 = nameFromId ndb $ fromJust maybeId1 in if isNothing maybeName1 then Nothing else let maybeId2 = idFromRank rdb 2 in if isNothing maybeId2 then Nothing else let maybeName2 = nameFromId ndb $ fromJust maybeId2 in if isNothing maybeName2 then Nothing else let maybeId3 = idFromRank rdb 3 in if isNothing maybeId3 then Nothing else let maybeName3 = nameFromId ndb $ fromJust maybeId3 in if isNothing maybeName3 then Nothing else Just ( fromJust maybeName1, fromJust maybeName2, fromJust maybeName3 )
Haskell には case 文があるので、効果的に使えば下記のように改善することができる。
しかし case 文の入れ子も本質的には地獄である。こんなことなら手続き型言語を使えばよいのではないか。
topThreeC :: RankDB -> NameDB -> Maybe (Name,Name,Name) topThreeC rdb ndb = case idFromRank rdb 1 of Nothing -> Nothing Just id1 -> case nameFromId ndb id1 of Nothing -> Nothing Just n1 -> case idFromRank rdb 2 of Nothing -> Nothing Just id2 -> case nameFromId ndb id2 of Nothing -> Nothing Just n2 -> case idFromRank rdb 3 of Nothing -> Nothing Just id3 -> case nameFromId ndb id3 of Nothing -> Nothing Just n3 -> Just (n1,n2,n3)
そこで Maybeモナド登場。
Maybe型のモナドとしての性質を使うと、上記とまったく同じ意味の関数を下記の内容だけで記述することができる。
モナドとしてのMaybe型には、失敗するかもしれない計算どうしを組み合わせるときは、一部でも失敗したらすべてが失敗したことにする
という性質が最初から組み込まれているためである。
topThreeM :: RankDB -> NameDB -> Maybe (Name,Name,Name) topThreeM rdb ndb = do id <- idFromRank rdb 1 n1 <- nameFromId ndb id id <- idFromRank rdb 2 n2 <- nameFromId ndb id id <- idFromRank rdb 3 n3 <- nameFromId ndb id return (n1,n2,n3)
全部のせておく。
import Data.Maybe (isNothing,fromJust) type ID = Int type Rank = Int type Name = String type RankDB = [(Rank,ID)] type NameDB = [(ID,Name)] idFromRank :: RankDB -> Rank -> Maybe ID idFromRank db rk = lookup rk db nameFromId :: NameDB -> ID -> Maybe Name nameFromId db id = lookup id db topThree :: RankDB -> NameDB -> Maybe (Name,Name,Name) topThree rdb ndb = let maybeId1 = idFromRank rdb 1 in if isNothing maybeId1 then Nothing else let maybeName1 = nameFromId ndb $ fromJust maybeId1 in if isNothing maybeName1 then Nothing else let maybeId2 = idFromRank rdb 2 in if isNothing maybeId2 then Nothing else let maybeName2 = nameFromId ndb $ fromJust maybeId2 in if isNothing maybeName2 then Nothing else let maybeId3 = idFromRank rdb 3 in if isNothing maybeId3 then Nothing else let maybeName3 = nameFromId ndb $ fromJust maybeId3 in if isNothing maybeName3 then Nothing else Just ( fromJust maybeName1, fromJust maybeName2, fromJust maybeName3 ) topThreeC :: RankDB -> NameDB -> Maybe (Name,Name,Name) topThreeC rdb ndb = case idFromRank rdb 1 of Nothing -> Nothing Just id1 -> case nameFromId ndb id1 of Nothing -> Nothing Just n1 -> case idFromRank rdb 2 of Nothing -> Nothing Just id2 -> case nameFromId ndb id2 of Nothing -> Nothing Just n2 -> case idFromRank rdb 3 of Nothing -> Nothing Just id3 -> case nameFromId ndb id3 of Nothing -> Nothing Just n3 -> Just (n1,n2,n3) topThreeM :: RankDB -> NameDB -> Maybe (Name,Name,Name) topThreeM rdb ndb = do id <- idFromRank rdb 1 n1 <- nameFromId ndb id id <- idFromRank rdb 2 n2 <- nameFromId ndb id id <- idFromRank rdb 3 n3 <- nameFromId ndb id return (n1,n2,n3) main = do print $ topThree [(1,101),(2,102),(3,103)] [(101,"1st"),(102,"2nd"),(103,"3rd")] print $ topThree [(1,101),(4,102),(3,103)] [(101,"1st"),(102,"2nd"),(103,"3rd")] print $ topThree [(1,101),(2,102),(3,103)] [(101,"1st"),(104,"4th"),(103,"3rd")] print $ topThreeC [(1,101),(2,102),(3,103)] [(101,"1st"),(102,"2nd"),(103,"3rd")] print $ topThreeC [(1,101),(4,102),(3,103)] [(101,"1st"),(102,"2nd"),(103,"3rd")] print $ topThreeC [(1,101),(2,102),(3,103)] [(101,"1st"),(104,"4th"),(103,"3rd")] print $ topThreeM [(1,101),(2,102),(3,103)] [(101,"1st"),(102,"2nd"),(103,"3rd")] print $ topThreeM [(1,101),(4,102),(3,103)] [(101,"1st"),(102,"2nd"),(103,"3rd")] print $ topThreeM [(1,101),(2,102),(3,103)] [(101,"1st"),(104,"4th"),(103,"3rd")]