Lesson 8 / 25

Type Classes and Instances

Use and define type classes, derive instances and understand constraints.

Ad hoc polymorphism

A type class declares operations that types can support, like an interface: class Show a where show :: a -> String. An instance provides the implementation for a specific type: instance Show Paise where .... Functions use constraints to require capabilities: maximum :: Ord a => [a] -> a works for any ordered type. Common classes: Eq (==), Ord (compare, <), Show (debug strings), Read (parsing, best avoided for user input), Num, Integral, Fractional, Enum, Bounded, Semigroup (<>) and Monoid (mempty), and the structural classes Functor, Foldable, Traversable, Applicative and Monad. Many instances can be derived automatically (deriving (Show, Eq, Ord)), and extensions such as DeriveGeneric, DeriveAnyClass and DerivingVia derive more. Classes can have default methods and superclasses (class Eq a => Ord a). Unlike interfaces in OOP, instances can be added to existing types after the fact, but avoid orphan instances (instances defined in neither the class's nor the type's module), which can conflict.

Defining a class and instances

A HasArea class, a custom Show and a Monoid for money.

data Shape = Circle Double | Rect Double Double
  deriving (Show, Eq)

class HasArea a where
  area :: a -> Double
  describe :: a -> String
  describe x = "area " ++ show (area x)          -- default method

instance HasArea Shape where
  area (Circle r) = pi * r * r
  area (Rect w h) = w * h

newtype Paise = Paise Int
  deriving (Eq, Ord)

instance Show Paise where
  show (Paise p) = "Rs " ++ show (p `div` 100) ++ "." ++ pad (p `mod` 100)
    where
      pad n = if n < 10 then '0' : show n else show n

instance Semigroup Paise where
  Paise a <> Paise b = Paise (a + b)

instance Monoid Paise where
  mempty = Paise 0

largestArea :: HasArea a => [a] -> Double
largestArea = maximum . (0 :) . map area

main :: IO ()
main = do
  putStrLn (describe (Rect 3 4))                   -- area 12.0
  print (largestArea [Circle 1, Rect 2 5])         -- 10.0
  print (mconcat [Paise 4950, Paise 19900, Paise 5])   -- Rs 248.55
  print (maximum [Paise 300, Paise 120])           -- Rs 3.00

Job qualifications

A type class is a qualification such as "can drive". A function that needs a driver states the constraint (Drives a =>) without caring who it is, and each type proves it holds the qualification by providing an instance.

Quick check: What does the constraint in `sort :: Ord a => [a] -> [a]` mean?

  • a must be a list
  • a must be Int
  • sort returns an Ord
  • sort works for any type a that has an Ord instance
Answer

sort works for any type a that has an Ord instance — Constraints restrict type variables to types with the required instances.