17
votes

Is there a way to rely on methods defined in case class in a trait? E.g., copy: the following doesn't work. I'm not sure why, though.

trait K[T <: K[T]] {
  val x: String
  val y: String
  def m: T = copy(x = "hello")
  def copy(x: String = this.x, y: String = this.y): T
}

case class L(val x: String, val y: String) extends K[L]

Gives:

error: class L needs to be abstract, since method copy in trait K of type 
(x: String,y: String)L is not defined
           case class L(val x: String, val y: String) extends K[L]
                      ^
3
Why would you think that the compiler can can come up with an implementation (that does what you want) for any method signature?Raphael
I don't really care—ideally I wouldn't even need to define a copy in the trait, and would be able to mark somehow that the trait can only be mixed in to case classes. Is that possible?Aaron Yodaiken
I don't get what you want. A method either has to be defined or abstract, there is no "maybe".Raphael
I'd like to be able to define a method in a trait which relies on a case class' copy method. Not being able to do so will lead to a lot of duplicated code, for no real reason.Aaron Yodaiken
How can it rely on a method that is not there?Raphael

3 Answers

4
votes

I suppose that having method with name copy in trait instructs compiler to not generate method copy in case class - so in your example method copy is not implemented in your case class. Below short experiment with method copy implemented in trait:

scala> trait K[T <: K[T]] {                                                                                   
     | val x: String                                                                                          
     | val y: String                                                                                          
     | def m: T = copy(x = "hello")                                                                           
     | def copy(x: String = this.x, y: String = this.y): T = {println("I'm from trait"); null.asInstanceOf[T]}
     | }

defined trait K

scala> case class L(val x: String, val y: String) extends K[L]                                                
defined class L

scala> val c = L("x","y")                                                                                     
c: L = L(x,y)

scala> val d = c.copy()                                                                                       
I'm from trait
d: L = null
16
votes

A solution is to declare that your trait must be applied to a class with a copy method:

trait K[T <: K[T]] {this: {def copy(x: String, y: String): T} =>
  val x: String
  val y: String
  def m: T = copy(x = "hello", y)
}

(unfortunately you can not use implicit parameter in the copy method, as implicit declaration is not allowed in the type declaration)

Then your declaration is ok:

case class L(val x: String, val y: String) extends K[L]

(tested in REPL scala 2.8.1)

The reason why your attempt does not work is explained in the solution proposed by other users: your copy declaration block the generation of the "case copy" method.

1
votes

You can run repl with $scala -Xprint:typer. With parameter -Xprint:typer you can see what exactly happening when you create trait or class. And you will see from output that method "copy" not created, so compiler requests to define it by yourself.