Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Case to case inheritance in Scala

Tags:

scala

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?

like image 424
Kamal Banga Avatar asked Feb 26 '15 14:02

Kamal Banga


People also ask

Can a case class extends another case class?

The answer is simple: Case Class can extend another Class, trait or Abstract Class.

How is inheritance used in Scala?

Scala Inheritance When a class inherits from another, it means it extends another. We use the 'extends' keyword for this. This lets a class inherit members from the one it extends and lets us reuse code. The class that extends is the subclass, the child class, or the derived class.

Does Scala support inheritance?

Multiple Inheritance: In Multiple inheritance ,one class can have more than one superclass and inherit features from all parent classes. Scala does not support multiple inheritance with classes, but it can be achieved by traits.


2 Answers

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)
like image 115
lmm Avatar answered Sep 20 '22 13:09

lmm


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.

like image 39
Juh_ Avatar answered Sep 22 '22 13:09

Juh_