73

Is there a way to extend a case class without constantly picking up new vals along the way?

For example this doesn't work:

case class Edge(a: Strl, b: Strl)
case class EdgeQA(a: Strl, b: Strl, right: Int, asked: Int) extends Edge(a, b)

"a" conflicts with "a", so I'm forced to rename to a1. But I don't want all kinds of extra public copies of "a" so I made it private.

case class Edge(a: Strl, b: Strl)
case class EdgeQA(private val a1: Strl, private val b1: Strl, right: Int, asked: Int) extends Edge(a, b)

This just doesn't seem clean to me... Am I missing something?

Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
LaloInDublin
  • 5,379
  • 4
  • 22
  • 26

4 Answers4

77

As the previous commenter mentioned: case class extension should be avoided but you could convert your Edge class into a trait.

If you want to avoid the private statements you can also mark the variables as override

trait Edge{
  def a:Strl
  def b:Strl
}

case class EdgeQA(override val a:Strl, override val b:Strl, right:Int, asked:Int ) extends Edge

Don't forget to prefer def over val in traits

David Riccitelli
  • 7,491
  • 5
  • 42
  • 56
bajohns
  • 1,276
  • 10
  • 4
  • 13
    We lose the advantage of the Edge as a standalone class: before it could be instantiated and it had the object Factory methods – WestCoastProjects Feb 03 '14 at 01:43
  • 7
    but I still need to redeclare a and b. What if the trait had some default value for a and be and I wish to use those values themselves. – Sohaib Jun 11 '15 at 09:19
  • 7
    Can you explain why def is preferred in traits? – Wolfsblvt Sep 03 '15 at 08:10
  • Please do explain your last statement. Is it because the implementation can be either a function or a variable? – BAR Sep 10 '15 at 19:50
  • 8
    `"case class extension should be avoided`". Can you please explain why, @bajohns? – Kevin Meredith Dec 23 '16 at 13:46
  • 1
    [this](https://stackoverflow.com/questions/19642053/when-to-use-val-or-def-in-scala-traits) is an explanation on why to use def vs val – Bill'o Mar 15 '18 at 12:45
  • Good but not perfect, we can't perfom sanity check on the variables this way without duplicating the code. Fortunately, Scala 3 will allow trait values as shown in [this example](https://github.com/lampepfl/dotty-example-project/blob/master/src/main/scala/TraitParams.scala). – 6infinity8 Apr 26 '18 at 11:43
59

This solution offers some advantages over the previous solutions:

trait BaseEdge {
  def a: Strl
  def b: Strl
}
case class Edge(a:Strl, b:Strl) extends BaseEdge
case class EdgeQA(a:Strl, b:Strl, right:Int, asked:Int ) extends BaseEdge

In this way:

  • you don't have redundant vals, and
  • you have 2 case classes.
david.perez
  • 6,090
  • 4
  • 34
  • 57
15

Case classes can't be extended via subclassing. Or rather, the sub-class of a case class cannot be a case class itself.

nickgroenke
  • 1,472
  • 9
  • 12
  • 4
    So why are there `final case class`es? The `final` _LocalModifier_ is what prevents the extension of a `case class`, not some other language prohibition. See §5.2, p.63 of the [Scala Reference](http://www.scala-lang.org/files/archive/nightly/pdfs/ScalaReference.pdf) – Jeffrey Aguilera Feb 04 '14 at 23:01
  • 9
    Final prevents extending a case class with anything ( i.e. case and not case classes), in addition to the built-in restriction to extend case classes with case classes. – jhegedus Mar 19 '14 at 11:53
5

Starting in Scala 3, traits can have parameters:

trait Edge(a: Strl, b: Strl)
case class EdgeQA(a: Strl, b: Strl, c: Int, d: Int) extends Edge(a, b)
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190