Search code examples
scalamultiple-inheritancecase-classcompiler-generated

For `case class Cc(a: Int, b: Int) extends MyTraitA`, where does `MyTraitA` appear with `Product` and `Serializable` traits?


Because of inheritance linearization in Scala, I would like to understand how traits I specify for a case class are ordered relative to the two traits automatically generated and added by the Scala compiler; i.e. Product with Serializable (and disappointingly, it's not ProductN[...] as of 2.12).

I've searched through pretty thoroughly and I haven't found it directly addressed. Given the following:

case class Cc(a: Int, b: Int) extends MyTraitA with MyTraitB

Following the Scala compiler automatic code generation, which of these is the correct assumption about the resulting inheritance ordering?

  1. case class Cc(a: Int, b: Int) extends MyTraitA with MyTraitB with Product with Serializable
  2. case class Cc(a: Int, b: Int) extends Product with Serializable with MyTraitA with MyTraitB

And then, I have an additional question. What undesirable or unexpected effects could occur if I were to explicitly extend Product2[...] to the case class? Here are the two above pieces of code repeated with Product2[...] inserted:

  1. case class Cc(a: Int, b: Int) extends MyTraitA with MyTraitB with Product2[Int, Int] with Product with Serializable
  2. case class Cc(a: Int, b: Int) extends Product with Serializable with MyTraitA with MyTraitB with Product2[Int, Int]

IOW, is there any sort of undesirable interactions because both Product and Product2 appear together?


Solution

  • Thanks to deep reading a link provided in a comment by Bogdan Vakulenko, the explicit answer to the first question is item 2:

    case class Cc(a: Int, b: Int) extends Product with Serializable with MyTraitA with MyTraitB
    

    And thanks, again, to Bogdan Vakulenko, the answer to the second question is nothing undesirable should occur when adding the Product2[Int, Int] trait given it extends the Product trait.


    There is a bonus answer which is very interesting. If it is desired to push the compiler generated interfaces to the back of the trait inheritance ordering, it's required the compiler generated interfaces instead be explicitly defined. And there are several ways to do this.

    The first and simplest is to change the original code which appeared like this (no reference to Product nor Serializable and have the compiler automatically generate them):

    case class Cc(a: Int, b: Int) extends MyTraitA with MyTraitB
    

    to appear like this (explicitly defining Product and Serializable at the tail of the trait list):

    case class Cc(a: Int, b: Int) extends MyTraitA with MyTraitB with Product with Serializable
    

    The second option is to add Product with Serializable to both/either of MyTraitA and/or MyTraitB as such:

    trait MyTraitA extends Product with Serializable
    trait MyTraitB extends Product with Serializable
    case class Cc(a: Int, b: Int) extends MyTraitA with MyTraitB
    

    This technique also results in desirable trait ordering.

    case class Cc(a: Int, b: Int) extends MyTraitA with MyTraitB with Product with Serializable
    

    Finally, integrating Product2[Int, Int] is as simple as explicitly defining everything knowing any overlaps will be automatically resolved via the excellent multiple inheritance resolution strategies provided by default within the Scala compiler:

    case class Cc(a: Int, b: Int) extends MyTraitA with MyTraitB with Product2[Int, Int] with Product with Serializable {
      override def _1: Int = a
      override def _2: Int = b
    }