12

I would like to know if it is possible to abstract the copy method of case classes. Basically I have something like sealed trait Op and then something like case class Push(value: Int) extends Op and case class Pop() extends Op.

The first problem: A case class without arguments/members does not define a copy method. You can try this in the REPL.

scala> case class Foo()
defined class Foo

scala> Foo().copy()
<console>:8: error: value copy is not a member of Foo
       Foo().copy()
             ^

scala> case class Foo(x: Int)
defined class Foo

scala> Foo(0).copy()
res1: Foo = Foo(0)

Is there a reason why the compiler makes this exception? I think it is rather unituitive and I would expect every case class to define a copy method.

The second problem: I have a method def ops: List[Op] and I would like to copy all ops like ops map { _.copy() }. How would I define the copy method in the Op trait? I get a "too many arguments" error if I say def copy(): Op. However, since all copy() methods have only optional arguments: why is this incorrect? And, how do I do that correct? By making another method named def clone(): Op and write everywhere def clone() = copy() for all the case classes? I hope not.

Joa Ebert
  • 6,565
  • 7
  • 33
  • 47
  • 1
    The answer to your question, "...is possible to abstract the copy method of case classes", ignoring other aspects of your question is: Yes. You want to check out a concept called lenses. There is a great video by Edward Kmett on lenses in Scala and Scalaz is a library that implements them as a very rich library. There is also a compiler plugin (Lensed) that generates the necessary boilerplate rather than handwriting it. Hopefully scalamacros.org will alleviate this some day soon. – Tony Morris Jan 04 '12 at 10:43

5 Answers5

12

You seem to be confusing copy with clone. The goal of copy is to make an almost identical copy, but with something changed. What that something might be depends on the parameters of the case class, so it's not possible to make it a common method.

In the case of case class X(), it doesn't make much sense to have a copy method, as there's nothing there to be changed.

On the other hand, clone is a Java method whose goal is to produce perfect copies of an object, which seems to be what you want.

Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • Properly used (having no mutable state), case classes with no properties make no sense, either, since every instance is indistinguishable from every other instance. – Randall Schulz May 26 '10 at 16:16
  • 1
    I do not agree. clone is not generated for case classes but copy is. And clone is not accessable for case classes by default. My intent with this question was to get around a copy and paste massacre. Speed is important so relfections are out of the game. The result? I wrote "override def clone() = copy()" and "override def clone() = CaseClass()" in 150+ case classes. IMHO this is far from optimal and could have been done much easier. – Joa Ebert May 26 '10 at 18:44
  • 1
    @Joa But if a case class has no parameters, each object in it is equal to all others. Except for internal vars and vals, which are not copied by `copy` anyway. – Daniel C. Sobral May 26 '10 at 19:24
  • (Immutable) case classes with no properties make sense; they are just ismorphic to Unit. – Tony Morris Jan 04 '12 at 10:40
  • 1
    @TonyMorris Yes, though I prefer case objects for that. It's calling `copy` on a case class with no properties that I don't see any sense in. – Daniel C. Sobral Jan 04 '12 at 13:55
9
  1. What would be the benefit of a compiler generated copy method for case classes without any arguments? This would just return a new Foo, and not copy anything.
  2. To quote Lukas Rytz (I believe he implemented it):
The copy methods are only generated if there is no member named"copy" in the class, directly defined or inherited.
Mirko Stocker
  • 2,232
  • 16
  • 22
2

Upvoted Ben's answer. But what if you wanted to something like this:

sealed trait Op 
case class Push(value: Int, context:String) extends Op
case class Pop(context:String) extends Op

val stackOps = List(Push(3, "foo"), Pop("foo"))

def copyToContext(newContext:String, ops:List[Op]): List[Op] = {
    // ... ?
}

val changedOps = copyToContext("bar", stackOps)

// would return: List(Push(3, "bar"), Pop("bar"))
missingfaktor
  • 90,905
  • 62
  • 285
  • 365
huynhjl
  • 41,520
  • 14
  • 105
  • 158
2

As Mirko correctly pointed out, you cannot really abstract over copy method. I support Daniel's view, that cloning may be what you want, although I would wrap it with some helper code to reduce boilerplate.

You can define a mixin trait with copy functionality and just mix it into your case classes then:

trait ClonableAs[T] extends Cloneable { this: T => 
  def makeClone() = super.clone().asInstanceOf[T]
}

case class Foo(i: Int) extends ClonableAs[Foo]

List(Foo(1), Foo(2), Foo(3)).map(_.makeClone())

That way instead of adding an identical method to each of your case classes, you make them extend the helper trait, which makes them cleaner and saves you some keystrokes.

On the other hand, the cloning would make no sense for immutable objects, so I infer your classes have mutable state. I would advise you to reconsider if you really cannot make them immutable, and use that type of cloning only at last resort. Immutability will protect yourself from a class of errors.

Przemek Pokrywka
  • 2,219
  • 17
  • 23
1

Why do you need to create identical copies of your case class instances? Case classes are, by default, immutable so can be safely shared.

In any case, I don't think you can do what you're asking with default parameters:

scala> trait Op { def copy():Op }          
defined trait Op

scala> case class Op1(v:Int) extends Op    
<console>:6: error: class Op1 needs to be abstract, since method copy in trait Op of type ()Op is not defined
       case class Op1(v:Int) extends Op

The compiler doesn't create methods with all combinations of the optional parameters in the defining class. The default values are inserted in the place where the method is called.

Ben Lings
  • 28,823
  • 13
  • 72
  • 81
  • Because I need to reference the created objects by instance and not by value. It does not have to do anything with sharing or a mutable/immutable discussion. By the way: you posted a question, not an answer. – Joa Ebert May 26 '10 at 13:24
  • If you don't need value semantics, why are you using a case class? – Ben Lings May 26 '10 at 13:48
  • Pattern matching for instance. Basically I like all of their features but I have to have by-reference equality and by-value equality as well. As misto already pointed out, the compiler won't generate a copy() method at all in the example. – Joa Ebert May 26 '10 at 14:03