On instance hierarchies in Haskell

101 views Asked by At

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? :/

1

There are 1 answers

4
Mark Seemann On BEST ANSWER

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