user811602
user811602

Reputation: 1354

Extend case class from another case class

I have two case class Person and Employee

case class Person(identifier: String) {}

case class Employee (salary: Long) extends Person {}

I am getting following error:

Unspecified value parameters: identifier: String
Error: case class Employee has case ancestor Person, but case-to-case inheritance is prohibited. To overcome this limitation, use extractors to pattern match on non-leaf nodes

I am new to Scala and not able to understand what I have to do.

Version: Scala : 2.11

Upvotes: 6

Views: 11906

Answers (3)

SergGr
SergGr

Reputation: 23798

Case classes in Scala add several different features but often you really use only some of them. So the main question you need to answer is which features you really need. Here is a list based on the spec:

  • remove the need to type val before field names/constructor params
  • remove the need for new by adding apply method to the companion object
  • support for pattern matching by adding unapply method to the companion object. (One of nice things of Scala is that pattern-matching is done in a non-magical way, you can implement it for any data type without requiring it to be a case class)
  • add equals and hashCode implementations based on all the fields
  • add toString implementations
  • add copy method (useful because case classes are immutable by default)
  • implement Product trait

A reasonable guess of the equivalent for case class Person(identifier: String) is

class Person(val identifier: String) extends Product  {

  def canEqual(other: Any): Boolean = other.isInstanceOf[Person]

  override def equals(other: Any): Boolean = other match {
    case that: Person => (that canEqual this) && identifier == that.identifier
    case _ => false
  }

  override def hashCode(): Int = identifier.hashCode

  override def toString = s"Person($identifier)"

  def copy(newIdentifier: String): Person = new Person(newIdentifier)

  override def productElement(n: Int): Any = n match {
    case 0 => identifier
    case _ => throw new IndexOutOfBoundsException(s"Index $n is out of range")
  }

  override def productArity: Int = 1
}

object Person {
  def apply(identifier: String): Person = new Person(identifier)

  def unapply(person: Person): Option[String] = if (person eq null) None else Some(person.identifier)
}

case class Employee(override val identifier: String, salary: Long) extends Person(identifier) {}

Actually the main objections to inheriting from a case class and especially making a case class inheriting another one are the Product trait, copy and equals/hashCode because they introduce ambiguity. Adding canEqual partially mitigates the last problem but not the first ones. On the other hand in a hierarchy like yours, you probably don't need the copy method or Product implementation at all. If you don't use Person in pattern matching, you don't need unapply as well. Most probably all you really need case for is apply, toString and hashCode/equals/canEqual.

Upvotes: 4

Paweł Dawczak
Paweł Dawczak

Reputation: 9649

Unfortunately, I'm afraid it is not possible for case class to extend another case class.

The inheritance in "plain" classes would look like:

class Person(val identifier: String) {}

class Employee(override val identifier: String, salary: Long)
  extends Person(identifier) {}

val el = new Employee("abc-test", 999)
println(el.identifier) // => "abc-test"

If you would like to achieve a similar effect with case classes, you would need to reach out to traits:

trait Identifiable {
  def identifier: String
}

case class Person(identifier: String) extends Identifiable {}

case class Employee(identifier: String, salary: Long)
  extends Identifiable {}

val el = Employee("abc-test", 999)
println(el.identifier) // => "abc-test"

Defining extractors

Extractor provides a way for defining a matching statement used in pattern matching. It is defined in an object in unaply method.

Let's consider the first example again adding support for extractors:

class Person(val identifier: String)

class Employee(override val identifier: String, val salary: Long)
  extends Person(identifier)

object Person {
  def unapply(identifier: String): Option[Person] = {
    if (identifier.startsWith("PER-")) {
      Some(new Person(identifier))
    }
    else {
      None
    }
  }
}

object Employee {
  def unapply(identifier: String): Option[Employee] = {
    if (identifier.startsWith("EMP-")) {
      Some(new Employee(identifier, 999))
    }
    else {
      None
    }
  }
}

Now, let's define a method that will define pattern matching using those extractors:

def process(anInput: String): Unit = {
  anInput match {
    case Employee(anEmployee) => println(s"Employee identified ${anEmployee.identifier}, $$${anEmployee.salary}")
    case Person(aPerson) => println(s"Person identified ${aPerson.identifier}")
    case _ => println("Was unable to identify anyone...")
  }
}

process("PER-123-test") // => Person identified PER-123-test
process("EMP-321-test") // => Employee identified EMP-321-test, $999
process("Foo-Bar-Test") // => Was unable to identify anyone...

Upvotes: 9

Dima
Dima

Reputation: 40508

Inheriting from case classes (even with regular non-case classes, which is not prohibited) is a bad idea. Check this answer out to get an idea why.

You Person does not need to be a case class. It actually does not need to be a class at all:

trait Person {
   def identifier: String
}
case class Employee(identifier: String, salary: Long) extends Person

Upvotes: 2

Related Questions