Keep your code clean with algebraic data types (ADTs)

Recently, Daniel Westheide wrote an interesting post about the abuse of the Option type in Scala. You can find it here. I couldn’t agree more with Daniel.

This short story is another example that demonstrates how using Option is not always the best option (pun intended).

I’m developing an advertising service for a customer using Scala. A simplified version of the Ad data structure is the following:

final case class Ad(
    headline: String, 
    description1: String, 
    description2: String
)

At some point they told me we need to support, by adding the headline2 field, two types of ad: standard and expanded. They said: “If headline, description1, and description2 are used, it is a standard ad. If headline, headline2, and description1 are used it is an expanded one. Users won’t include headline2 when the ad is intended to be standard, and won’t include description2 when the ad is intended to be expanded.”

The optionality of the headline2 and description2 fields could put you in the wrong direction. Indeed, you could think of changing the data structure as follows:

final case class Ad(
    headline: String, 
    headline2: Option[String], 
    description1: String, 
    description2: Option[String]
)

This may sound fine but you could reach a much cleaner and type-safe solution using a simple Algebraic Data Type (ADT). Here’s how:

sealed trait Ad

object Ad {
  final case class Standard(
      headline: String, 
      description1: String, 
      description2: String
  ) extends Ad
  
  final case class Expanded(
      headline: String, 
      headline2: String, 
      description1: String
  ) extends Ad
}

This way you don’t need to inspect the headline2 and/or description2 value to find out the ad type you’re dealing with. Indeed, the ad type is encoded in the type system which is always a desiderata.

Bottom line: use Option with caution.