Product with Serializable

by Cody Allen on May 09, 2018

technical

A somewhat common Scala idiom is to make an abstract type extend Product with Serializable. There isn’t an obvious reason to do this, and people have asked me a number of times why I’ve done this. While I don’t think that Product or Serializable are particularly good abstractions, there’s a reason that I extend them.

Let’s say that I’m writing a simple enum-like Status type:

object EnumExample1 {
  sealed abstract class Status
  case object Pending extends Status
  case object InProgress extends Status
  case object Finished extends Status
}

Now let’s create a Set of statuses that represent incomplete items:

import EnumExample1._
val incomplete = Set(Pending, InProgress)
// incomplete: scala.collection.immutable.Set[Product with Serializable with EnumExample1.Status] = Set(Pending, InProgress)

Here, I didn’t give in explicit return type to incomplete and you may have noticed that the compiler inferred a somewhat bizarre one: Set[Product with Serializable with Status]. Why is that?

The compiler generally tries to infer the most specific type possible. Usually this makes sense. If you write val x = 3 you probably don’t want it to infer val x: Any = 3. And in the example above, I didn’t want the return type for incomplete to be inferred as Any or even Set[Any]. However, the compiler was a bit too clever and realized that not only is every item in the set an instance of Status, they are also instances of Product and Serializable since every case object (and case class) automatically extends Product and Serializable. Therefore, when it calculates the least upper bound (LUB) of the types in the set, it comes up with Product with Serializable with Status.

While there’s nothing inherently wrong with the return type of Product with Serializable with Status, it is verbose, it wasn’t what I intended, and in certain situations it might cause inference issues. Luckily there’s a simple workaround to get the inferred type that I want:

object EnumExample2 {
  // note the `extends` addition here
  sealed abstract class Status extends Product with Serializable
  case object Pending extends Status
  case object InProgress extends Status
  case object Finished extends Status
}
import EnumExample2._
val incomplete = Set(Pending, InProgress)
// incomplete: scala.collection.immutable.Set[EnumExample2.Status] = Set(Pending, InProgress)

Now since Status itself already includes Product and Serializable, Status is the LUB type of Pending, InProgress, and Finished.