An existential type hides a value’s concrete type while preserving operations guaranteed by constraints. This allows values such as D Int and D Float to share one list, even though their type parameters differ. section 11.1.1
Use existential quantification when a container must accept different concrete types but expose a common capability:
- Enable ExistentialQuantification and define a constructor such as MkE :: a -> E; every wrapped value then has type E. section 11.1.1
- Add a constraint when consumers need to use the hidden value. ShowData can hide any Show value and still implement showThis. section 11.1.1
- Define a capability-preserving instance for the wrapper, such as Pitchable Pitch delegating toFrequency to the wrapped pitch. section 11.1.2
- Use standalone deriving when the existential wrapper needs a Show instance, and store differently typed values in ordinary lists. section 11.1.2
Heterogeneous pitches
A musical composition can wrap a chromatic pitch, a frequency, and a semitone value in one [Pitch] list. Although their representations differ, map toFrequency produces [440.0, 440.0, 440.0]. NoteStructure can then use Pitch without retaining its original type parameter. section 11.1.2
Exception packaging
SomeException packages any Exception instance behind an existential type. A handler can catch SomeFailure specifically, or accept SomeException to catch every exception, although broad handling is discouraged. section 14.4
ghci> data ShowData = forall a. (Show a) => ShowThis a
ghci> :{
ghci| showThis :: ShowData -> String
ghci| showThis (ShowThis x) = show x
ghci| :}
ghci> showValues = [ShowThis (1 :: Int), ShowThis ("Hi" :: String)]
ghci> :t showValues
showValues :: [ShowData]
ghci> map showThis showValues
["1","\"Hi\""]
Inspect a complete GHCi example that wraps Int and String values as ShowData and processes both through showThis. section 11.1.1
