haskellfunctional-programmingclass-hierarchy

On instance hierarchies in Haskell


I have the following definitions:

data Egg = ChickenEgg | ChocolateEgg
  deriving Show
  
data Milk = Milk Int -- amount in litres
  deriving Show
  
class Price a where
  price :: a -> Int

instance Price Egg where
  price ChickenEgg = 20
  price ChocolateEgg = 30

instance Price Milk where
  price (Milk v) = 15 * v

The above compiles and runs OK. However, I need to be able to do something like that:

price (Just ChickenEgg) -- 20
price [Milk 1, Milk 2] -- 45
price [Just ChocolateEgg, Nothing, Just ChickenEgg] -- 50
price [Nothing, Nothing, Just (Milk 1), Just (Milk 2)]  -- 45

My best attempt for price (Just ChickenEgg) was:

instance Price (Maybe Egg) where
  price Nothing = 0
  price (Just ChickenEgg) = 20
  price (Just ChocolateEgg) = 30

Q: What am I doing wrong here? :/


Solution

  • If this is really what you want to do, you can write an instance like this:

    instance Price a => Price (Maybe a) where
      price Nothing = 0
      price (Just x) = price x
    

    This enables not only price (Just ChickenEgg), but getting the price of any Maybe Price instance:

    ghci> price (Just ChickenEgg)
    20
    ghci> price (Just (Milk 2))
    30
    

    This, however, doesn't feel quite like idiomatic Haskell to me. There are certainly use cases where something like this is useful (e.g. QuickCheck does this a lot), but often it's safer to keep the Maybe or list structure intact, and instead rely on built-in combinators:

    ghci> price <$> Just ChickenEgg
    Just 20
    

    You can do the same with your other examples, without adding more type class instances:

    ghci> sum $ price <$> [Milk 1, Milk 2]
    45
    ghci> sum $ mapMaybe (fmap price) [Just ChocolateEgg, Nothing, Just ChickenEgg]
    50
    ghci> sum $ mapMaybe (fmap price) [Nothing, Nothing, Just (Milk 1), Just (Milk 2)]
    45