Skip to content
AgileLogics
7 min read

Type Classes

Prerequisites: Algebraic data types

A type class describes a set of operations that many types support. It's similar to an interface in Java or a trait in Rust, but types can join a class after they're defined, and classes can have default methods.

You've already seen constraints like Eq a => and Num a =>. The most common standard classes:

Class Operations Meaning
Eq ==, /= Can be compared for equality
Ord <, compare, max, ... Can be ordered (requires Eq)
Show show Can be turned into a String
Read read Can be parsed from a String
Enum succ, [a ..] Can be enumerated
Bounded minBound, maxBound Has a smallest and largest value
Num +, *, negate, ... Numeric

Deriving instances

For most classes, GHC can write the code for you:

data Priority = Low | Medium | High
  deriving (Show, Read, Eq, Ord, Enum, Bounded)

main :: IO ()
main = do
  print (Low < High, maximum [Medium, Low, High])
  print [minBound .. maxBound :: Priority]
  print (succ Low)
  print (read "High" :: Priority)
(True,High)
[Low,Medium,High]
Medium
High

Derived Ord orders constructors in the order they are declared.

Writing instances by hand

data Temperature = Celsius Double | Fahrenheit Double

toCelsius :: Temperature -> Double
toCelsius (Celsius c) = c
toCelsius (Fahrenheit f) = (f - 32) * 5 / 9

instance Eq Temperature where
  a == b = abs (toCelsius a - toCelsius b) < 0.001

instance Show Temperature where
  show (Celsius c) = show c ++ " C"
  show (Fahrenheit f) = show f ++ " F"

instance Ord Temperature where
  compare a b = compare (toCelsius a) (toCelsius b)

main :: IO ()
main = do
  print (Celsius 100 == Fahrenheit 212)
  print [Celsius 25, Fahrenheit 50]
  print (maximum [Celsius 30, Fahrenheit 100, Celsius 20])
True
[25.0 C,50.0 F]
100.0 F
  • instance Eq Temperature where supplies the class methods for the type. Eq only needs ==; /= has a default definition.
  • Hand-written instances let equality mean what makes sense for your domain — here, two temperatures are equal if they're physically equal.

Constraints in your own functions

describeAll :: Show a => [a] -> String
describeAll xs = unwords (map show xs)

largest :: Ord a => [a] -> Maybe a
largest [] = Nothing
largest xs = Just (maximum xs)

countEqual :: Eq a => a -> [a] -> Int
countEqual x = length . filter (== x)

main :: IO ()
main = do
  putStrLn (describeAll [1, 2, 3])
  putStrLn (describeAll [True, False])
  print (largest "haskell", largest ([] :: [Int]))
  print (countEqual 'a' "banana")
1 2 3
True False
(Just 's',Nothing)
3

Write the most general constraint that works: countEqual only needs Eq, so it works for any type with equality.

Common mistakes

  • Forgetting deriving (Show) and then trying to print a value: "No instance for (Show Color)". Add the deriving clause.
  • Deriving Eq on a type whose "sameness" isn't structural (like Temperature above) — write the instance by hand instead.