Haskell Type Classes — Brief ☧
"In whom are hid all the treasures of wisdom and knowledge."
— Colossians 2:3 (KJV)
Deep version → | Back to Category Theory →
Q: You know how a USB port works with many different devices — a
keyboard, a mouse, a flash drive — even though each device does
something completely different? They all conform to the same interface.
The port does not care about the specifics; it just knows the device
speaks USB. Is there a way to express this idea in a type system?
A: That is exactly what a type class is. A type class declares
an interface — a set of operations that any conforming type must provide.
Each type that "implements" the class supplies its own version of those
operations. Different types, one shared interface.
-- For God so loved the world that he gave his only begotten Son, -- that whoever believes in him should not perish but have eternal life. -- John 3:16 -- The interface: every gift can be exercised class GiftChirho a where exerciseChirho :: a -> String -- Prophecy: one type, its own implementation data ProphecyChirho = ProphecyChirho String instance GiftChirho ProphecyChirho where exerciseChirho (ProphecyChirho msg) = "Thus saith the Lord: " ++ msg -- Healing: different type, same interface data HealingChirho = HealingChirho String instance GiftChirho HealingChirho where exerciseChirho (HealingChirho who) = who ++ " is healed!"Q: That reminds me of interfaces in Java or protocols in Swift.
What makes type classes special?
A: Two things. First, you can add a type class instance after the
type is defined — you do not need to modify the original code. Second,
type classes form a hierarchy.
Functor(the ability to map overcontents) is the simplest.
Applicativebuilds on it (apply wrappedfunctions).
Monadextends that further (chain computations whereeach step depends on the previous result). Each level adds capability,
like building blocks stacking on each other.
Q: Can you give me a concrete everyday example of this hierarchy?
A: Sure. Think about working with a list — an array
of values:
- Functor: you can
mapa function over every element (double each number).- Applicative: you can apply a list of functions to a list of values (every combination).
- Monad: each element produces a new list, and you flatten the results (like a search that branches at every step).
Each level is strictly more powerful, and each includes the abilities
of the levels below it.
The Core Hierarchy
| Type Class | Core Operation | Plain-English Analogy |
|---|---|---|
| Functor | fmap — transform contents | Apply one function to every item in a box |
| Applicative | <*> — apply wrapped function | Apply multiple boxed functions to boxed values |
| Monad | >>= (bind) — chain computations | Each step's output decides the next step's input |
| Semigroup | <> — combine two values | Merge two linked lists end-to-end |
| Monoid | mempty + <> — identity + combine | An empty list is the identity: merging with it changes nothing |
The hierarchy in this table is one of the most important concepts in typed functional programming. Notice how each type class builds on the ones above it: Functor lets you transform contents, Applicative lets you combine multiple containers, and Monad lets you chain dependent computations. Semigroup and Monoid capture the idea of combining values, which is fundamental to everything from string concatenation to parallel reduction. Understanding this hierarchy gives you a vocabulary for describing computational patterns that recur across every domain of programming.
Connection to our system: Our semiring_chirho.rs implements the Semiring
type class with four different instances: Bool (for exact constraint solving), Probability (for soft inference), Tropical (for optimization), and Counting (for solution enumeration). Each semiring provides
its own plus_chirho (how alternatives combine) and times_chirho (how conjunctions combine), unified by one interface. The power of this design is that you write
the constraint propagation algorithm once, and the type class picks the right
implementation at compile time. When the FPGA runs in Boolean mode, times is AND and plus is OR. When the CPU runs in Probability mode for differentiable inference, times is multiplication and plus is probabilistic sum. Same algorithm, different interpretations -- exactly the kind of principled abstraction that type classes were designed for.
Soli Deo Gloria