Home About Contact
Haskell , Monad

Haskell / 二つの Maybe Int の数値を比較したい

たとえば、コーヒーメニューアイテムの名前とその価格があったときに、値段が高い方を取り出したい、とする。 ただし、価格が不明なアイテムもあるので、価格の型は Mayb Int になっている。 それらを上手に取り扱いたい場合を考える。

テストするデータモデル:

type Price = Maybe Int
data Item = Item String Price deriving (Show)

itemCA = Item "Caffe Americano" (Just 400)
itemCM = Item "Caffe Misto" (Just 500)
itemPPR = Item "Pike Place Roast" Nothing

このように価格が不明なアイテムが紛れ込んでいる場合に備えて、そこを Maybe Int 型にしておく。 このようなデータから2つのアイテムを取り出して価格比較を行いたい。

そのために、2つのアイテムを適用すると値段が高い方を返す関数を考える。 一方または両方の価格が不明な場合を想定し、そのときは、Nothing を返せるように 返り値の型を Maybe Item とする。

higherPriceItem :: Item -> Item -> Maybe Item

方法A) デフォルト価格を 0 として計算する

価格が不明な場合には 価格を 0 と見なして処理するコードを考えました。

higherPriceItem item0 item1
  | (price item0) == Nothing = Nothing
  | (price item1) == Nothing = Nothing
  | (maybe 0 id (price item0)) < (maybe 0 id (price item1)) = Just item1
  | otherwise = Just item0
  where
    price :: Item -> Price
    price (Item _ v) = v

Maybe a から a を取り出す maybe 関数

ghci で確認:

> :load price.hs
> higherPriceItem itemCA itemCM
Just (Item "Caffe Americano" (Just 400))
> higherPriceItem itemCA itemPPR
Nothing

方法B) do 構文を使う例

それぞれのMaybe Int から Int を取り出して比較して価格が高い方を返す部分に do 構文を使う場合。

higherPriceItem :: Item -> Item -> Maybe Item
higherPriceItem item0 item1 = do
  price0 <- price item0
  price1 <- price item1
  if price0 < price1
    then Just item0
    else Just item1
  where
    price :: Item -> Price
    price (Item _ v) = v

方法A と比べての利点としては、Nothing の場合の対処をいちいち記述しないで済む点。

まとめ

完成したコード: price.hs

type Price = Maybe Int
data Item = Item String Price deriving (Show)

itemCA = Item "Caffe Americano" (Just 400)
itemCM = Item "Caffe Misto" (Just 500)
itemPPR = Item "Pike Place Roast" Nothing

higherPriceItem :: Item -> Item -> Maybe Item

{-
higherPriceItem item0 item1
  | (price item0) == Nothing = Nothing
  | (price item1) == Nothing = Nothing
  | (maybe 0 id (price item0)) < (maybe 0 id (price item1)) = Just item1
  | otherwise = Just item0
  where
    price :: Item -> Price
    price (Item _ v) = v
-}

higherPriceItem item0 item1 = do
  price0 <- price item0
  price1 <- price item1
  if price0 < price1
    then Just item0
    else Just item1
  where
    price :: Item -> Price
    price (Item _ v) = v