Algebraic Data Types: Precision and Expressiveness
Algebraic data types (ADTs) form the foundation of modeling in typed functional languages, and their influence now extends far beyond.
An ADT is constructed by composing two fundamental operations:
- The product (conjunction of fields, “A and B”)
- The sum (disjunction of variants, “A or B”)
This simple algebra generates remarkable expressiveness. Where object-oriented languages offer classes with inheritance and interfaces, ADTs provide a more direct and rigorous approach to describing the exact shape of data that a program manipulates.
Product Types and Sum Types
Product types (records, structs, tuples) capture entities composed of multiple simultaneous attributes. A User has a name and an email and a createdAt. Nothing revolutionary here; all languages offer this construction.
The true power of ADTs lies in sum types: discriminated unions, variants, enums with associated data.
PaymentStatus = Pending | Completed { transactionId } | Failed { reason }Each variant carries exactly the data relevant to its case. This modeling closes the space of possibilities: there is no fourth state, no hybrid combination.
Pattern Matching and Exhaustiveness
Pattern matching fully exploits this structure.
When deconstructing an ADT, the compiler requires exhaustiveness: each variant must be handled. Forgetting a case generates a compilation error, not a bug in production.
This static guarantee transforms risky refactorings into mechanical operations: add a variant to the sum type, then let the compiler guide you to all the places in the code that must be adapted. Add a Refunded status to your payment type: every switch that forgets it becomes a compilation error, fixed in minutes, rather than a bug discovered in production weeks later.
The type becomes a source of truth that automatically propagates changes throughout the codebase. Logic errors become type errors. This confidence in refactoring translates directly to velocity: the team can evolve the business model without fearing silent breakage of existing features.
Encoding Business Invariants
ADTs allow encoding business invariants directly into the data structure.
Rather than an Order with optional fields whose validity depends on a status field, we model an explicit state machine:
DraftOrder | ConfirmedOrder | ShippedOrdereach with its own shape.
The invalid state “shipped order without shipping date” becomes inexpressible: not forbidden by runtime validation, but structurally impossible. This is the essence of Make Illegal States Unrepresentable: ADTs provide the vocabulary for the type system to capture business rules, not just primitive types.
Growing Adoption
TypeScript has democratized ADTs in the JavaScript ecosystem via discriminated unions and automatic narrowing. Rust has made them idiomatic with its data-carrying enum and exhaustive match. Even Java, long limited to simple enums, has introduced sealed classes and pattern matching.
This convergence is not fortuitous: ADTs address a universal need for precise modeling. They reduce the gap between the developer’s thinking (“this value is either this or that”) and the code that expresses it.
By making the mental model explicit and verifiable, they eliminate an entire class of errors where code and intent silently diverge.
Want to dive deeper into these topics?
We help teams adopt these practices through hands-on consulting and training.
or email us at contact@evryg.com