A heterogeneous data structure stores values with different concrete types while presenting a common interface. In Haskell, existential quantification hides each value’s type parameter, allowing one list to contain values such as frequencies, chromatic notes, and semitones. section 11.0 section 11.1
Build a useful heterogeneous type by following three design steps:
- Wrap values in an existential constructor such as `Pitch`, hiding the underlying type while retaining required constraints such as `Pitchable` and `Show`. section 11.1.1 section 11.1.2
- Define operations through the shared interface. A `Pitchable Pitch` instance can delegate `toFrequency` to the wrapped value. section 11.1.2
- Use the wrapper in a recursive structure. Replacing a type parameter with `Pitch` lets notes, pauses, sequences, and groups share one composition type. section 11.1.2 section 11.1
Mixed musical pitches
A list can contain a chromatic note, a frequency, and a semitone value: `pitches = [Pitch (a 4), Pitch (440 :: Hz), Pitch (0 :: Semitone)]`. Its type is `[Pitch]`, and mapping `toFrequency` produces `[440.0, 440.0, 440.0]`. The values differ internally but support the same operation. section 11.1.2
Listing 11.1 A data type for structuring notes
data Pitch = forall a. (Pitchable a, Show a) => Pitch a #1 deriving instance Show Pitch #2 instance Pitchable Pitch where toFrequency (Pitch p) = toFrequency p #3
Inspect the existential `Pitch` definition, its constraints, derived display support, and delegated `Pitchable` instance. section 11.1.2
ghci> :set -XExistentialQuantification ghci> data E = forall a. MkE a ghci> :t MkE MkE :: a -> E ghci> :t MkE (1 :: Int) MkE (1 :: Int) :: E ghci> :t MkE (1 :: Float) MkE (1 :: Float) :: E ghci> :t [MkE (1 :: Int), MkE (2 :: Float)] [MkE (1 :: Int), MkE (2 :: Float)] :: [E]
Compare ordinary parametrized lists with an existential constructor in GHCi, including the type error and the successful mixed list. section 11.1.1
