I have an abstract class which I extend and make numerous case classes. Now I want to copy instances of those case classes just changing first parameter, so I use case class' copy
method.
Since I have to do this for all case classes that have been extended from common abstract class, rather than doing it for all, I tried to make it general and made the abstract class a case class.
Then Scala gives me this:
case class Octopus has case ancestor Organism, but case-to-case inheritance is prohibited. To overcome this limitation, use extractors to pattern match on non-leaf nodes.
Code:
abstract class Organism(legs: Int)
case class Octopus(override val legs: Int, weight: Double, ...)
case class Frog(override val legs: Int, ...)
def clone(o: Organism) = o.copy(legs = -1)
This is what I want to do. But if I can't make the clone
method work, then I will have to do copying for both Octopus
& Frog
.
Any suggestions, to decrease this verbosity?
You can't abstract over case class' copy
methods generically. I'd suggest using Lenses from Shapeless or Monocle:
trait Organism { def legs: Int }
// monocle @Lenses uses a macro to generate lenses
@Lenses case class Octopus(override val legs: Int, weight: Double, ...)
extends Organism
@Lenses case class Frog(val legs: Int, ...) extends Organism
def clone[O <: Organism](o: O, legsLens: Lens[O, Int]): O =
legsLens.set(-1)(o)
val myOctopus = Octopus(8, 2.4, ...)
val myFrog = Frog(2, ...)
// use the generated Lenses
val cloneOctopus: Octopus = clone(myOctopus, Octopus.legs)
clone(myFrog, Frog.legs)
Using only standard scala there is no such generic copy method on abstract (super) class: how would it know how all subclasses can be cloned/copied? Especially that new subclasses could be added in the future.
To my knowledge, the two main approaches to implement such abstract method are:
1) make a function that match-case on all subclasses:
def clone(o: Organism) = o match {
case o: Octopus => o.copy(legs = -1)
case f: Frog => f.copy(legs = -1)
}
Then each time a new subclass is added, it needs to be added in this functions. This is most appropriate for use with sealed abstract class.
2) add a makeClone
method to the abstract API (the name clone
being reserved):
abstract class Organism(legs: Int){
def makeClone(legNumber: Int): Organism
}
case class Octopus(legs: Int, weight: Double) extends Organism(legs) {
def makeClone(legNumber: Int) = this.copy(legs = legNumber)
}
Note that while the function in (1) always returns an Organism
, here the method Octopus.makeClone
returns an Octopus
.