🏷️ Type Class Emulation

How OCaml's module system achieves what Haskell does with type classes

typeclass.ml

Haskell ↔ OCaml Mapping

OCaml doesn't have type classes, but its module system is powerful enough to express the same patterns:

Haskell ConceptOCaml EquivalentExample
Type class declarationmodule typemodule type SHOW = sig ... end
Instance declarationmodule : SIGmodule ShowInt : SHOW = struct ... end
Constrained functionFunctormodule Sort (O : ORD) = struct ... end
Superclass constraintincludemodule type ORD = sig include EQ ... end
DerivingFunctor compositionShowList(ShowInt)
Runtime dispatchFirst-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 include in module types — ORD includes EQ.
  • No orphan instances: Since instances are named modules, there's no orphan instance problem.