Type Classes as Shared Interfaces
A type class declares a set of function signatures that any conforming type must implement, functioning as an interface contract rather than a container of data. class Eq a where (==) :: a -> a -> Bool; (/=) :: a -> a -> Bool says: any type a that wants to be an instance of Eq must provide an == (and /=) function comparing two values of that type. This is Haskell's mechanism for ad-hoc polymorphism -- the same function name == behaves differently for Int, Bool, or a custom Color type, with the compiler picking the right implementation based on the concrete type involved, resolved via an implicit 'dictionary' of functions passed behind the scenes.
Cricket analogy: Just as the ICC requires every international team to field exactly eleven players and follow the same over-limit rules regardless of whether it's Australia or Afghanistan, a type class like Eq requires every instance type to implement ==, with the actual comparison logic differing per type just as playing styles differ per team.
Declaring Instances and Deriving
You make a type conform to a class by writing an instance declaration, instance Eq Color where Red == Red = True; Blue == Blue = True; _ == _ = False, providing concrete implementations for every method the class requires. For classes like Eq, Ord, Show, and Read whose behavior follows a mechanical, structural pattern, GHC can generate the instance automatically via deriving (Eq, Show, Ord) appended to a data declaration, saving you from writing boilerplate comparison and printing code by hand for every constructor.
Cricket analogy: Writing a custom instance Eq Color where ... is like a franchise league writing its own bespoke tie-breaker rules instead of using the ICC default, while deriving (Eq) is like adopting the standard net-run-rate tie-breaker automatically without drafting new regulations.
data Color = Red | Green | Blue
deriving (Eq, Show, Ord) -- auto-generated ==, show, compare
class Describable a where
describe :: a -> String
describe _ = "no description available" -- default method
instance Describable Color where
describe Red = "warm and bold"
describe Green = "calm and natural"
describe Blue = "cool and deep"
-- A constrained polymorphic function: works for ANY Ord instance
biggest :: Ord a => [a] -> a
biggest = maximum
main :: IO ()
main = do
print (Red == Red) -- True (from deriving Eq)
print (Red < Blue) -- True (from deriving Ord, ctor order)
putStrLn (describe Green) -- "calm and natural"
print (biggest [3, 1, 4, 1, 5 :: Int]) -- 5
Class Constraints on Polymorphic Functions
A function signature like biggest :: Ord a => [a] -> a carries a class constraint before the =>, meaning biggest works for a list of any type a, as long as that type has an Ord instance -- it is polymorphic, but not for every possible type, only for types that support ordering. Under the hood, GHC implements this by silently passing a 'dictionary' of the required functions (like compare and <=) alongside the polymorphic argument, which is why calling biggest [Red, Blue] only compiles if Color derives or defines Ord; leaving that deriving clause off produces a clear compile-time error rather than a runtime surprise.
Cricket analogy: A tournament eligibility rule like 'any bowler with a first-class average under 25 may be selected' constrains selection to a subset of players who satisfy that condition, mirroring how Ord a => [a] -> a constrains biggest to only types a that satisfy the Ord capability.
Commonly Used Standard Type Classes
A handful of type classes recur throughout everyday Haskell: Eq for equality (==, /=), Ord for ordering (compare, <, >=, and maximum/minimum/sort built on top of it), Show for converting a value to a human-readable String (used implicitly by GHCi and print), Num for arithmetic operations like + and *, and Functor for mapping a function over a wrapped value via fmap (which works uniformly across Maybe, lists, Either, and many other types). Recognizing these five by sight is enough to read the vast majority of type signatures in real Haskell code, since almost every constraint you encounter -- Ord a =>, Show a =>, Num a => -- is drawn from this small, well-known set.
Cricket analogy: A commentary team relies on a small set of recurring stat categories -- strike rate, economy rate, average -- to describe almost any player, the same way most Haskell signatures lean on a small recurring set of classes like Eq, Ord, Show, and Num.
Class methods can carry default implementations, like (/=) x y = not (x == y) inside the Eq class declaration itself, so an instance only needs to define == and gets a correct /= for free -- overriding the default is only necessary when a more efficient direct implementation is possible.
deriving (Ord) bases its comparison purely on the order constructors are listed in the data declaration -- data Priority = Low | Medium | High deriving (Ord) makes Low < High true because Low is listed first, not because of any inherent numeric meaning, so reordering the constructors silently changes the sort order everywhere the derived Ord instance is used.
- A type class declares a set of function signatures (like
==forEq) that any conforming type must implement via aninstancedeclaration. - Type classes enable ad-hoc polymorphism: the same function name behaves differently per type, resolved through an implicit dictionary passed by the compiler.
deriving (Eq, Show, Ord)on adatadeclaration auto-generates mechanical instances instead of requiring hand-written boilerplate.- A constraint like
Ord a =>before a signature's=>restricts a polymorphic function to only types that implement that class. Eq,Ord,Show,Num, andFunctorare the five type classes that appear in the overwhelming majority of everyday Haskell signatures.- Class declarations can supply default method implementations, so an instance only needs to define the minimal required methods.
- Derived
Ordinstances depend on constructor declaration order, which can silently produce an unintended sort order if not considered carefully.
Practice what you learned
1. What is a Haskell type class most analogous to in mainstream programming languages?
2. What does `deriving (Eq, Show)` on a `data` declaration do?
3. In `biggest :: Ord a => [a] -> a`, what does the `Ord a =>` part mean?
4. Given `data Priority = Low | Medium | High deriving (Ord)`, why is `Low < High` true?
5. What mechanism does GHC use under the hood to resolve which `==` implementation to call for a polymorphic, constrained function?
Was this page helpful?
You May Also Like
The Haskell Type System
How Haskell's static, strong type system uses type signatures, inference, and kinds to catch errors before a program ever runs.
Algebraic Data Types
How Haskell builds custom types from sum types and product types using data declarations, pattern matching, records, and recursion.
Maybe and Either
How Haskell uses the Maybe and Either types to represent optional values and recoverable errors explicitly, without null or exceptions.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics