17

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]
                      ^
Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
Aaron Yodaiken
  • 19,163
  • 32
  • 103
  • 184
  • Why would you think that the compiler can can come up with an implementation (that does what you want) for any method signature? – Raphael Mar 17 '11 at 17:51
  • 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 Mar 17 '11 at 17:59
  • I don't get what you want. A method either has to be defined or abstract, there is no "maybe". – Raphael Mar 17 '11 at 18:35
  • 1
    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 Mar 17 '11 at 20:03
  • How can it rely on a method that *is not there*? – Raphael Mar 19 '11 at 13:08
  • [A trait probably cannot require being applied only to a case class](http://stackoverflow.com/questions/33674602/can-a-scala-self-type-enforce-a-case-class-type) – matanster Nov 27 '15 at 17:34

3 Answers3

17

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.

lex82
  • 11,173
  • 2
  • 44
  • 69
Nicolas
  • 24,509
  • 5
  • 60
  • 66
  • 5
    While this works in a very few, limited cases, most copy methods will rely on abstract types, which aren't allowed outside the refinement. – Aaron Yodaiken Mar 18 '11 at 13:59
  • 3
    Moreover, the number of parameters for method "copy" must exactly match the number of case class parameters used in the subclass. Thus, trait K would not be compatible to case class L if L had 3 parameters instead of 2. – Stefan Endrullis Jan 28 '12 at 10:08
5

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
Mariusz
  • 292
  • 3
  • 9
  • 1
    See also http://www.scala-lang.org/node/6369 and the specification section 5.3.2 on case classes where this is explictly mentioned – Arjan Blokzijl Mar 17 '11 at 18:06
  • 5
    Solution if only for saying there's no solution :( – Aaron Yodaiken Mar 18 '11 at 02:30
  • "A method named copy is implicitly added to every case class unless the class already has a member (directly defined or inherited) with that name, or the class has a repeated parameter." https://www.scala-lang.org/files/archive/spec/2.11/05-classes-and-objects.html#case-classes – Logan Feb 14 '20 at 19:20
1

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.

4e6
  • 10,696
  • 4
  • 52
  • 62