Reading — step 1 of 7
Learn
FP in Scala and the Scala docs treat algebraic data types (ADTs) as the foundation of domain modeling. The combo of sealed trait + case class is Scala's killer pattern for closed families of data.
Sum types — sealed trait + case classes
A value is exactly ONE of several alternatives:
sealed trait PaymentMethod
case class CreditCard(number: String, cvv: String) extends PaymentMethod
case class BankTransfer(account: String, routing: String) extends PaymentMethod
case class Cash(amount: Double) extends PaymentMethod
case object Free extends PaymentMethod // singleton — no payload
sealed— restricts subtypes to this file (compiler can verify exhaustiveness)- Each subtype is either a
case class(with data) orcase object(without) - The compiler warns on non-exhaustive matches
Pattern matching is now type-safe
def process(p: PaymentMethod): String = p match {
case CreditCard(num, _) => s"charging card ending ${num.takeRight(4)}"
case BankTransfer(acc, _) => s"transfer from $acc"
case Cash(amt) => s"cash $$$amt"
case Free => "no charge"
}
Add a new case (PayPal, etc.) to the trait → every match in the codebase issues a warning until updated. Refactor-safe.
Product types — case classes alone
A value contains ALL of several fields:
case class User(name: String, email: String, age: Int)
case class Address(street: String, city: String, zip: String)
Product types model "this AND that AND that." Sum types model "this OR that OR that."
Composing — sums of products
Real domains combine both:
sealed trait Event
case class UserSignup(userId: String, email: String, timestamp: Long) extends Event
case class OrderPlaced(orderId: String, userId: String, amount: Double) extends Event
case class ItemViewed(userId: String, itemId: String) extends Event
An Event is a sum (one of three) where each variant is a product (multiple fields). This is the canonical shape of domain models.
Encoding state machines
ADTs shine for modeling lifecycle:
sealed trait OrderState
case object Cart extends OrderState
case class PendingPayment(method: PaymentMethod) extends OrderState
case class Confirmed(txId: String) extends OrderState
case class Shipped(trackingNumber: String) extends OrderState
case class Delivered(deliveredAt: Long) extends OrderState
case class Cancelled(reason: String, cancelledAt: Long) extends OrderState
def nextState(state: OrderState, event: Event): OrderState = (state, event) match {
case (Cart, AddPayment(method)) => PendingPayment(method)
case (PendingPayment(_), PaymentSucceeded(tx)) => Confirmed(tx)
case (Confirmed(_), Ship(tn)) => Shipped(tn)
// ... etc
case (s, _) => s // ignore invalid transitions
}
Illegal transitions become COMPILE-TIME impossibilities — you can't construct Shipped from Cart because the case patterns don't match.
Compared to enum (Scala 3)
Scala 3 introduces native enum syntax that compiles to the same sealed-trait pattern:
// Scala 3 syntax:
enum PaymentMethod:
case CreditCard(number: String, cvv: String)
case BankTransfer(account: String, routing: String)
case Cash(amount: Double)
case Free
Functionally equivalent — just less ceremony.
Recursive ADTs — trees, expressions
sealed trait Tree[+A]
case object Leaf extends Tree[Nothing]
case class Node[A](value: A, left: Tree[A], right: Tree[A]) extends Tree[A]
def depth[A](t: Tree[A]): Int = t match {
case Leaf => 0
case Node(_, l, r) => 1 + math.max(depth(l), depth(r))
}
Trees, JSON, AST, expression evaluators — all natural ADTs.
Common mistakes
- Forgetting
sealed— without it, the compiler can't verify exhaustive matching; you can subclass anywhere. - Adding methods to one variant without thinking about all — better to define methods on the sealed trait or use pattern matching dispatch.
- Mixing inheritance with ADTs — keep the hierarchy SHALLOW. Two levels max for clarity.
- Big ADTs (10+ variants) — gets hard to maintain. Consider splitting into smaller ADTs.
- Using ADT for things that should be classes with behavior — ADTs are for DATA. Classes with state and methods are still classes.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…