Typeclasses, A First Look

Course 1 · Ch 8
Typeclasses, A First Look
1988 — the real historical ancestor of rust2-2's own trait system, closing the loop this course opened

Fundamentals closes with the chapter this whole course has been building toward: the mechanism behind every ==, every show, every print call used since Chapter 1 — and the real, documented origin of an idea already met on this site under a different name.

The Problem Typeclasses Solve — Ad-Hoc Polymorphism

== needs to behave differently for Int, for String, for a custom Shape — but should still be called the exact same way regardless. This is ad-hoc polymorphism: one function name, genuinely different implementations per type, dispatched based on which type is actually involved. A real, important contrast with haskell1-2's own parametric polymorphism (id :: a -> a), which works identically for every type with zero per-type customization at all.

class and instance — Defining a Typeclass

class MyEq a where myEq :: a -> a -> Bool instance MyEq Bool where myEq True True = True myEq False False = True myEq _ _ = False

A class declares a capability; an instance provides the actual per-type implementation. Any type can gain MyEq by writing its own instance — the capability isn't limited to types the language ships with.

Eq, Ord, and Show — The Standard Trio

data Shape = Circle Double | Rectangle Double Double deriving (Eq, Show) -- automatic instances for the straightforward case

Eq gives ==//=; Ord gives </>/compare; Show gives the conversion to String that has been powering every print/show call used throughout this entire course, now properly explained. deriving, already used without comment in haskell1-6 and haskell1-7, is GHC automatically writing the obvious structural instance for straightforward cases.

The Real Ancestor of rust2-2's Traits

Here's the closing reveal: Haskell's typeclasses, introduced in 1988, are the direct historical ancestor of rust2-2's own trait system. Genuinely the same core idea — a capability defined separately from any specific type, implemented per-type, dispatched by which type is actually involved. Rust's own designers have been explicit about this lineage. Stated plainly, closing the loop this entire course opened back in haskell1-1: this course exists to show where these ideas really came from, and this is the clearest case of all.

How Dispatch Actually Works — Dictionary Passing, Briefly

A real, honest technical note, kept light: GHC implements typeclass polymorphism by secretly passing a "dictionary" of the relevant functions alongside any call using a typeclass-constrained function. This is genuinely different from java1-5's/csharp1-5's own vtable-based dynamic dispatch, and different again from cpp2-1's/rust2-3's monomorphization — a real, distinct third mechanism, worth naming even without going deep here.

AspectJava interfaces (java1-6)Rust traits (rust2-2)Haskell typeclasses
ArrivalJava 1.0 (1996)Rust 1.0 (2015)1988 — the real origin
Dispatch mechanismvtablemonomorphization (usually)dictionary passing
Can implement for types you don't ownnoyes, with restrictionsyes
Use deriving whenever the default structural behavior is good enough
deriving (Eq, Ord, Show) covers the overwhelming majority of real cases — reach for a hand-written instance only when a type genuinely needs custom comparison or formatting logic beyond straightforward structural equality.
A typeclass constraint in a signature is required, not optional
Eq a => a -> a -> Bool — the Eq a => part is a real, load-bearing part of the type signature. Omitting it while the function body still uses == produces a genuine compile error the moment GHC checks the body against the (now too-weak) declared signature — a good, early checkpoint that catches a real mistake immediately.

Coding Challenges

Challenge 1

Define a data type Color = Red | Green | Blue with deriving (Eq, Show), and write an expression demonstrating both == comparing two Colors and show converting one to a String.

📄 View solution
Challenge 2

Define a typeclass Describable with a method describe :: a -> String, write instance declarations for two different types (e.g. Int and Bool), and call describe on a value of each.

📄 View solution
Challenge 3

Write a function that uses == inside its body but omit the Eq a => constraint from its type signature. Show the resulting compile error and explain why adding the constraint fixes it.

📄 View solution

Chapter 8 Quick Reference — Course 1 Complete

  • Ad-hoc polymorphism (one name, per-type implementations) is genuinely different from haskell1-2's own parametric polymorphism (identical for every type)
  • class declares a capability; instance provides the per-type implementation, for any type, including ones you don't own
  • Eq/Ord/Show are the standard trio — Show is what's been powering every print/show call since Chapter 1; deriving automates the straightforward case
  • Typeclasses (1988) are the real, documented historical ancestor of rust2-2's own trait system, arriving decades earlier
  • Dictionary passing is Haskell's own dispatch mechanism — a genuine third alternative to Java/C#'s vtables and C++/Rust's monomorphization
  • A typeclass constraint (Eq a =>) is a required, load-bearing part of a signature — omitting it while using == is a real compile error
  • Haskell Fundamentals is now complete. Course 2 (Intermediate/Advanced) begins with Functors — the first step toward the big monad reveal.