using custom data types in haskell

Viewed 700

I made these custom data types:

data BakedGoods = Muffin
                | Croissant
                | Cookie
                deriving (Eq, Show)

data Muffin = Blueberry
            | SaltedCaramel
            | Cheesecake

then I tried to use them in a function that returns the price of the BakedGoods:

price :: BakedGoods -> Int
price a 
  | a == Blueberry = 2

[...]

and I keep getting this error:

 Couldn't match expected type ‘BakedGoods’ with actual type ‘Muffin’

I get the point but unfortunately I have no idea how to fix it, do you have any ideas? :)

3 Answers

This is a bit confusing for beginners.

An algebraic datatype is of the form: data typename = constructor1 | constructor2 etc.

A type can have the same name as a constructor, even of a different type. So your code has two different Muffins - a type called Muffin (with constructors Blueberry, SaltedCaramel and Cheescake), and a constructor Muffin for the type BakedGoods. As far as the compiler is concerned, they are completely unconnected.

The code you want looks like this:

data BakedGoods = Muffin Muffin
    | ...

This means: The type BakedGoods has a constructor called Muffin, which contains one field of type Muffin.

You would then pattern-match it like so:

price :: BakedGoods -> Int
price (Muffin Blueberry) = 2
...

Which means: when the argument (which must be a BakedGoods) has a top-level constructor matching Muffin, whose field (of type Muffin) matches Blueberry, then return 2.

Happy Haskelling!

When you do

data BakedGoods = Muffin -- ...
data Muffin = Blueberry -- ...

You are not saying that BakedGoods's data constructors should include all of Muffin's. You are actually saying that you should create a new Muffin data constructor, completely unrelated to the Muffin type, and that Muffin data constructor is a BakedGood. If you want to merge the two, then declare them merged:

data BakedGoods = Blueberry
                | SaltedCaramel
                | Cheesecake
                | Croissant
                | Cookie

Then Blueberry would be a BakedGoods. But, if you want to retain the fact that they are muffins, then you can make a MuffinFlavor type and have your Muffin contain a MuffinFlavor:

data BakedGoods = Muffin MuffinFlavor
                | Croissant
                | Cookie

data MuffinFlavor = Blueberry | SaltedCaramel | Cheesecake

And now you can create muffins with Muffin Blueberry.

If you write

data BakedGoods = Muffin | …
then this is not a union of the data constructors of the Muffin type with other data constructors. You here simply say that BakedGoods has a data constructor named Muffin, the fact that there is a type named Muffin does not matter.

You can wrap a Muffin object in the Muffin data constructor, like:

data BakedGoods
  = Muffin Muffin  -- ← parameter for the Muffin data type
  | Croissant
  | Cookie
  deriving (Eq, Show)

Then you thus can perform pattern matching on the value wrapped in the Muffin data constructor:

price :: BakedGoods -> Int
price (Muffin Blueberry) = 2
Related