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?
case class Cc(a: Int, b: Int) extends MyTraitA with MyTraitB with Product with Serializable
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:
case class Cc(a: Int, b: Int) extends MyTraitA with MyTraitB with Product2[Int, Int] with Product with Serializable
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?
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
}