Theory
- Purpose. Type classes enable ad-hoc polymorphism: the same operator (e.g.
+,==) can have different implementations for different types, unlike parametric polymorphism where one implementation works for all types. - Constrained types. A functionโs type can be qualified with a class constraint, e.g.
(+) :: Num a => a -> a -> aRead โfor any a that is an instance of Num.โ Values whose type lacks the required instance cause the familiar โNo instance for โฆโ error.
- Standard library classes.
Eq(equality)Ord(total order, requiresEq)Num(arithmetic)Show(string conversion) /Read(string parsing) โ These form a small hierarchy: e.g. everyOrdmust also beEq.
- Ad-hoc vs. parametric. Where parametric functions (like
(++) :: [a] -> [a] -> [a]) ignore the element type, class-constrained functions choose behaviour per instance ((==) :: Eq a => a -> a -> Bool).
Implementation
Declaring a class
class Eq a where
(==), (/=) :: a -> a -> Bool
(x == y) = not (x /= y) -- default method
(x /= y) = not (x == y)Defaults mean an instance may define only one of the two methods.
Making a type an instance
data Color = Red | Green
instance Eq Color where
Red == Red = True
Green == Green = True
_ == _ = FalseMissing methods are filled by class defaults. GHC can derive many standard instances automatically:
data Color = Red | Green
deriving (Eq, Show)Using class constraints
A dictionary keyed by k needs equality to find entries:
get :: Eq k => k -> Dict k v -> Maybe vIf we optimize using ordering, we strengthen the constraint:
get :: Ord k => k -> Dict k v -> Maybe vDefining your own class
class Default a where
defValue :: a
instance Default Int where defValue = 0
instance Default [a] where defValue = []
get :: (Ord k, Default v) => k -> Dict k v -> v
get _ Empty = defValue
get key (Bind k v rest)
| key == k = v
| key < k = defValue
| otherwise = get key restDefault lets get produce a sensible fallback without modifying the dictionaryโs shape or API.