🏷️ Type Class Emulation
How OCaml's module system achieves what Haskell does with type classes
typeclass.mlHaskell ↔ OCaml Mapping
OCaml doesn't have type classes, but its module system is powerful enough to express the same patterns:
| Haskell Concept | OCaml Equivalent | Example |
|---|---|---|
| Type class declaration | module type | module type SHOW = sig ... end |
| Instance declaration | module : SIG | module ShowInt : SHOW = struct ... end |
| Constrained function | Functor | module Sort (O : ORD) = struct ... end |
| Superclass constraint | include | module type ORD = sig include EQ ... end |
| Deriving | Functor composition | ShowList(ShowInt) |
| Runtime dispatch | First-class modules | (module S : SHOW with type t = a) |
Type Classes in This Demo
📝 Show
module type SHOW = sig
type t
val show : t → string
end
Convert any type to a string representation. Instances for int, float, string, bool, and derived lists.
⚖️ Eq
module type EQ = sig
type t
val equal : t → t → bool
val not_equal : t → t → bool
end
Structural equality. Provides both equal and not_equal.
📊 Ord
module type ORD = sig
include EQ
val compare : t → t → ordering
val lt : t → t → bool
...
end
Total ordering, extending Eq. Enables generic sorting.
🔄 Functor
module type FUNCTOR = sig
type 'a t
val fmap : ('a → 'b) → 'a t → 'b t
end
Map a function over a container. Instances for list and option.
➕ Monoid
module type MONOID = sig
type t
val empty : t
val append : t → t → t
val concat : t list → t
end
Associative binary operation with identity. String, IntSum, and List instances.
Side-by-Side Comparison
Haskell
class Show a where
show :: a -> String
instance Show Int where
show = Prelude.show
instance Show a => Show [a] where
show xs = "[" ++ intercalate ", " (map show xs) ++ "]"
printList :: Show a => [a] -> IO ()
printList = putStrLn . show
OCaml
module type SHOW = sig
type t
val show : t -> string
end
module ShowInt : SHOW with type t = int = struct
type t = int
let show = string_of_int
end
module ShowList (S : SHOW) : SHOW with type t = S.t list = struct
type t = S.t list
let show xs = "[" ^ String.concat "; " (List.map S.show xs) ^ "]"
end
module PrintList (S : SHOW) = struct
let print_list xs =
let module SL = ShowList(S) in
print_endline (SL.show xs)
end
Interactive Playground
Build your own type class instances and see how they compose:
Output will appear here...
Key Takeaways
- Explicit > Implicit: OCaml requires you to pass module instances explicitly (or via functors). Haskell resolves them implicitly. This is a trade-off: more verbose, but no surprises.
- First-class modules bridge the gap — they allow runtime dispatch on type class instances, similar to Haskell's dictionary-passing.
- Functor composition (e.g.,
ShowList(ShowInt)) is OCaml's answer to derived instances. - Superclasses are expressed via
includein module types —ORDincludesEQ. - No orphan instances: Since instances are named modules, there's no orphan instance problem.