4

My domain classes are case classes and I have general engine/transformer classes that operate on them. I make use of case class functionality such as copy() and would like to do that while treating the classes generically, here is a simple example:

trait Fruit { val color: String }

case class Apple (color: String = "red") extends Fruit

case class Banana (color: String = "yellow") extends Fruit

case class Orange (color: String = "orange") extends Fruit

val apple = new Apple
val newApple1 = apple.copy(color = "green") // compiles fine

object Transformer {
  def apply(fruit: Fruit): Fruit = {
    fruit.copy(color = "green") // does not compile, "Cannot resolve symbol copy"
  }
}

val newApple2 = Transformer(apple)

newApple2

In this example the first call to copy() works fine, but trying to do the same kind of operation within the apply() method of the Transformer class does not, I am assuming because Scala does not know that Fruit is a case class and thus has the runtime-generated copy() method.

I've tried various typecasts, for example making Fruit an abstract class or having it extend scala.Product (which internally is the closest ancestor to the case class) but this doesn't make the copy() method available.

I would rather not implement the copy method myself if I can avoid it, though with reflection or shapeless it can probably be done in a generic way that would work.

There are a few other similar questions here on StackOverflow but I have not see an answer that addresses this, thanks for any info.

EDIT: My question is similar to this recent question however the answers there did not satisfy me for these reasons:

  • The accepted solution suggested implementing a copyWithId() method, or, in other words, a special-purpose copy() method that takes one or more specific parameters, whereas I want to use the very versatile case class copy() method which accepts variable parameters.
  • The solutions proposed there use shapeless or lenses and a lot of Scala "magic" which it seems to me should not be necessary since, as my example shows, the copy() implementation that I'm after is already in scope, but is just not available because the correct data type can't be inferred. I'm hoping instead to gain access to the copy() with the right kind of typecast.
Uncle Long Hair
  • 2,719
  • 3
  • 23
  • 33
  • 1
    I saw that answer and don't believe it addresses my question, I edited my question to explain why. – Uncle Long Hair Jun 05 '17 at 20:47
  • The problem here is that all your example `Fruit` instances _happen_ to have a `copy()` method but they don't _have_ to have it. You could create a `class Grape( ...` that extends `Fruit` and, because it's not a case class, it won't have a `copy()` method unless you give it one. One solution is to use pattern matching to identify exactly what type has been passed to `apply()` and then call its `copy()` method if it has one, but that's not the clean/concise code you're looking for. – jwvh Jun 05 '17 at 21:25
  • My domain classes all have a copy() method because they are case classes, and if there were a way to define Fruit as a trait or abstract class that indicated this, with a type or ancestor, or to include the signature for the copy() method somehow, then it seems that methods that took Fruit as a parameter would know that copy is available. I don't think that this question has been adequately answered on SO if you could un-mark it as a duplicate I'd like to get input from the community, thanks. – Uncle Long Hair Jun 06 '17 at 13:00
  • You need 5 votes to reopen. Right now you have 4 (mine is one of them), but asking a new, clearer, question might be the better way to go. I suspect what you want might not be possible. As [this Q/A](https://stackoverflow.com/questions/33674602/can-a-scala-self-type-enforce-a-case-class-type) demonstrates, it _is_ possible to require all Fruits be case classes but that still doesn't make `copy()` a member method of `Fruit`. – jwvh Jun 06 '17 at 17:32
  • Perhaps this is doable with a macro to implicitly create a typeclass? i.e. `trait Foo[A] { def copyAndChangeColor(a: A, color: String): A }` and `implicit def makeFoo[A <: Fruit]: Foo[A] = macro makeFooImpl[A]` where makeFooImpl would construct an expression instantiating a `Foo` that calls the `copy` method appropriately... but all this seems way more complicated than just defining an abstract `copyWithColor(color: String)` method on the `Fruit` trait and implementing it in all the subclasses. It's not clever or short, but it's very simple. – Joe K Jun 23 '17 at 00:27

0 Answers0