6

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

user811602
  • 1,314
  • 2
  • 17
  • 47
  • achieve what? what language is this? – Mahmoud Hanafy Dec 31 '18 at 11:32
  • @MahmoudHanafy I have corrected statement – user811602 Dec 31 '18 at 11:34
  • I recommend to read some basic course or book on Scala before asking questions, so that your questions are specific enough to be answered. – Suma Dec 31 '18 at 11:40
  • at-least add comment for reason of down voting so that I can correct myself – user811602 Dec 31 '18 at 11:41
  • @Suma can you please tell why this question is not specific – user811602 Dec 31 '18 at 11:42
  • 1
    the error message tell you that "case-to-case" class is prohibited, so you have to change class Person, read this: https://stackoverflow.com/questions/35261243/if-case-class-inheritance-is-prohibited-how-to-represent-this – Mahmoud Hanafy Dec 31 '18 at 11:44
  • 1
    @user811602 You wrote "not able to understand what I have to do". From your question is it not clear what you want to achieve. Most likely you want to derive one class from another, but this: 1) cannot be done for case classes (for case classes composition is preferred over inheritance) 2) is done with a bit different syntax (you need to pass arguments to the base class constructor). I think any basic Scala tutorial would teach you that more conveniently than asking here. – Suma Dec 31 '18 at 11:50
  • 1
    Example almost identical to what you are trying to do from Scala Cookbook by Alvin Alexander: https://www.oreilly.com/library/view/scala-cookbook/9781449340292/ch04s11.html – Suma Dec 31 '18 at 11:52

3 Answers3

7

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...
Paweł Dawczak
  • 9,519
  • 2
  • 24
  • 37
  • there is statement in error "To overcome this limitation, use extractors to pattern match on non-leaf nodes". Is it same thing that you mentioned? – user811602 Dec 31 '18 at 12:37
  • Hey @user811602, sorry for delayed response. Extractors are a slightly different beast and I'm not sure if you need one given only as little code as you've shown, but I've just updated the answer with an example for you to consider. Hope this helps! – Paweł Dawczak Jan 03 '19 at 07:19
4

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.

Suma
  • 33,181
  • 16
  • 123
  • 191
SergGr
  • 23,570
  • 2
  • 30
  • 51
2

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
Dima
  • 39,570
  • 6
  • 44
  • 70