The goal is to define a data type by cases, where
- one can add new cases to the data type and
- (one can add) new functions over the data type,
- without recompiling existing code, and
- while retaining static type safety.
Given this code
data Fruit = Apple | Banana showFruit :: Fruit -> String showFruit Apple = "apple" showFruit Banana = "banana"
We can easily add a new function to our code without needing to recompile our existing code
-- in another file... intFruit :: Fruit -> Int intFruit Apple = 0 intFruit Banana = 1
However, if we want to add another data constructor to
Fruit, we can only do so by updating
Fruit to include
Orange and then updating all of our functions to include
Orange as well:
data Fruit = Apple | Banana | Orange showFruit :: Fruit -> String showFruit Apple = "apple" showFruit Banana = "banana" showFruit Orange = "orange"
Fruit has already been compiled, we will need to recompile our code with the updated version of
Fruit. Moreover, if we do not update
intFruit, then we no longer have an exhaustive pattern match. Thus, these functions are no longer pure but are now partial functions.
The solution, then, is to be able to define data types in such a way that they "compose". The best way to compose data types is to group two types into one type via a type wrapper:
-- original file data Fruit = Apple | Banana -- new file data Fruit2 = Orange data FruitGrouper = -- ???
FruitGrouper be defined? A value of
FruitGrouper should only be one of 3 values:
- FruitGrouper Apple
- FruitGrouper Banana
- FruitGrouper Orange
We can define it using this approach:
data FruitGrouper = Fruit_ Fruit | Fruit2_ Fruit2
This approach will enable
intFruit to continue to work as expected. If we wanted to define a new function that uses both, we would pass in
-- original file. This cannot change once written! data Fruit = Apple | Banana showFruit :: Fruit -> String showFruit Apple = "apple" showFruit Banana = "banana" -- new file data Fruit2 = Orange data FruitGrouper = Fruit_ Fruit | Fruit2_ Fruit2 showAllFruit :: FruitGrouper -> String showAllFruit (Fruit_ appleOrBanana) = showFruit appleOrBanana showAllFruit (Fruit2_ Orange) = "orange"
Great! We have now seen how to solve a very simple version of this problem. Now, let's refine this approach a bit as preparation for a future harder problem.