0
votes

Why Scala case class copy method parameterised only with the variables defined in the case class?

The question based on on the Q&A:

Case class copy does not maintain state of an inherited trait

Short summary - when trait is defined with a field and extended by a case class, copy on the case class will create the new class instance only with the variables defined in case class, without the extended trait.

 trait A {
    var list: List[Int] = List()

    def add(element: Int) = {
      list = element :: list
    }
  }

  case class B(str: String) extends A {
    val b = B("foo")
    println("B1: " + b.list)

    b.add(1)
    b.add(2)
    b.add(3)
    println("B2: " + b.list)

    val b2 = b.copy(str = "bar")
    println("B3: " + b.list)
    println("B4: " + b2.list)
  }

Here, B4: () will be empty, while B3:(3,2,1)

2
Note that you should reconsider using case classes if you're going to embed mutable state in them - in most cases a case class should be immutable. - Jakub Kozłowski
BTW. I ran your code and B3 was (3,2,1) while B4 was empty (the inverse of what you said in the question). - Jakub Kozłowski
I find the comment by @cchantep in the linked question a completely satisfactory explanation, quote: 'No automatic/declarative way to handle that as "not good functional programming"' When implementing copy for case classes, they simply didn't think about this use case, and didn't prevent copy-codegen, and also didn't emit an error or at least a warning. Maybe you could just submit this as an issue in the scala compiler, so that it at least outputs a warning... - Andrey Tyukin
@JakubKozłowski Regarding mutability - of course. It's just for the sake of example. Regarding B3 & B$ - thanks. Of course it should have been the other way around. - Johnny
@AndreyTyukin - Interesting. So you think it's just a case of 'haven't thought about this use case'? - Johnny

2 Answers

2
votes

Because there is no reasonable way to do what you want consistently.

For your example, generated code for copy could be

def copy(str: String = this.str, list: List[Int] = this.list): B = {
  val newB = B(str)
  newB.list = list
  newB
}

Good enough. Now what happens if you change list to be private, or a val instead of a var? In both cases newB.list = ... won't compile, so what code should the compiler generate?

0
votes

If you really want to keep the value of list after copying (considering the mutability issue I mentioned in the comments of OP), you can write your own copy method.

case class B(str: String) extends A {
  def copy(str: String = this.str, list: List[Int] = this.list): B = {
    val newB = B(str)
    list.reverse.foreach(newB.add)
    newB
  }
}