Reputation: 5270
Say I define a trait and case class that extends it:
trait A {
var list: List[Int] = List()
def add(i: Int) = list = i :: list
}
case class B(str: String) extends A
If I then create an instance of B, modify the list and make a copy of it, the copy takes the values of the list as defined in trait A:
val b = B("foo")
b.add(3)
println("B: " + b.list)
println("Copied: " + b.copy(str = "bar").list)
Output:
B: List(3)
Copied: List()
I know this is probably not good functional programming practice anyway, but is there a way I can force the copy of B to take the altered version of the list for instance b?
Upvotes: 2
Views: 264
Reputation: 2841
You can accomplish this by declaring the list in the case class, and satisfy your trait's type by declaring a method. So:
trait A {
def list: List[Int]
}
case class B(i: Int, var list: List[Int]) extends A
And then your example:
scala> val b = B(2, List(1))
b: B = B(2,List(1))
scala> b.list = List(3)
b.list: List[Int] = List(3)
scala> println("B: " + b.list)
B: List(3)
scala> println("Copied: " + b.copy(i = 4).list)
Copied: List(3)
I don't know what you're doing that requires a mutable list in your class, but I imagine that even though this satisfies your example it might not satisfy whatever problem you're working on since:
scala> val a: A = b
a: A = B(2,List(3))
scala> a.list
res2: List[Int] = List(3)
scala> a.list = List(4)
<console>:15: error: value list_= is not a member of A
a.list = List(4)
When trying to view an instance of B
as an A
you won't be able to assign the list type. If you were dealing with an instance of A you'd have to do something like
scala> a match {
| case itsAB: B => itsAB.list = List(4); itsAB
| case other => other
| }
res3: A = B(2,List(4))
But either way, this is how you'd do it. Either that or you can follow phongnt's advice to create a non-case class and define a copy method yourself.
In a comment you said:
I failed to mention that the trait attribute needs to be modifiable with a reference to the trait itself
But this sounds like global mutable state to me. If you really need that, and I encourage you to not to then you can use an object:
object A {
@volatile
var list = List.empty[Int]
}
But I doubt this gets at what you're expressing in your question either since there is not an individual list still per instance here. So... looking at your updated question, I can only guess that something like this is what you're trying to accomplish:
object A {
@volatile
var list = List.empty[Int]
}
trait A {
def list = A.list
def add(i: Int) = A.list = i :: A.list
}
case class B(str: String) extends A
Which lets you:
scala> val b = B("hi")
b: B = B(hi)
scala> b.add(0)
scala> b.add(1)
scala> A.list
res4: List[Int] = List(1, 0)
But again, this is global mutable state, it's not a good thing. It's not threadsafe. So if there's a way for you to figure out your problem without doing this... I'd encourage you to do so.
Upvotes: 4
Reputation: 505
The copy
method is generated for you by the compiler when you declare a case class and it is only parameterised with the variables you define in the case class
Hence, you can definitely have copy
method do that for you by declaring the variable list
in the case class e.g. case class B(i: Int, list: List[Int])
. You will also need override val
modifier to satisfy scalac. However, scalac doesn't allow you to override a mutable variable, so you need to change the trait too, for example
trait A {
val list: List[Int] = List(1)
}
case class B(i: Int, override val list: List[Int]) extends A
Upvotes: 3