'Scala: How to pattern match a class type that extends a parent class?
I'm trying to use pattern matching to match classes that extend from a parent class, but also restrict the classes you can pass as a parameter using classType: Class[_ <: Animal]. Here's an example:
class Animal
class Dog extends Animal
class Cat extends Animal
def returnAnimalSubtypeObject(classType: Class[_ <: Animal]): Animal = {
classType match {
case _: Dog => new Dog()
case _: Cat => new Cat()
}
}
returnNewClass(Class[Dog])
But this returns the following error message:
<console>:16: error: pattern type is incompatible with expected type;
found : Dog
required: Class[_$1] where type _$1 <: Animal
case _: Dog => new Dog()
I think I'm not understanding something fundamental here since I'm still a beginner. Could someone explain what's the best approach to solving this?
Solution 1:[1]
The Class object for Dog is not a Dog: it's a Class.
With the proviso that there's almost certainly a better way to accomplish what you actually want to do, you could:
def returnAnimalSubtypeObject(clazz: Class[_ <: Animal]): Animal =
if (clazz == classOf[Dog]) new Dog()
else if (clazz == classOf[Cat]) new Cat()
else new Animal()
Solution 2:[2]
At the high level, doing what you are trying to do does not seem very useful even if it did work: returnNewClass(Class[Dog]) does not seem to have any advantages over just new Dog.
With that caveat, a proper way to define that function would be something like this:
import scala.reflect.ClassTag
def animal[T <: Animal : ClassTag]() = implictly[ClassTag[T]].runtimeClass.newInstance
Two interesting things here:
[T <: Animal : ClassTag] defines a type parameter T that must be a subclass of Animal and have a ClassTag avaialable for it at call site.
This definition is equivalent to def animal[T <: Animal](implicit ct: ClassTag[T]).
ClassTag is a scala object that provides a link between a scala type and the corresponding java class that implements it. So, once the ClassTag is avaialable, you can get a handle to .runtimeClass from it, and then instantiate it using java reflection api.
The way to call that function would be val d = animal[Dog], though, I'll say again that I just don't see how this adds any value over just new Dog() ...
Solution 3:[3]
It seems like you are trying to implement something like ADT(Abstract Data Type). I would prefer to use sealed trait or sealed abstract class since it's better in pattern matching.
see also: https://nrinaudo.github.io/scala-best-practices/definitions/adt.html
For purpose of understanding, I change some code here.
sealed trait Animal
case class Dog(name: String) extends Animal
case class Cat(name: String) extends Animal
object Animal {
def description(animal: Animal): String = animal match {
case Dog(name) => s"this is a dog named $name, it can bark"
case Cat(name) => s"this is a cat named $name, it can not bark like a dog"
}
}
scala> Animal.description(Dog("dog"))
val res0: String = this is a dog named dog, it can bark
scala> Animal.description(Cat("cat"))
val res1: String = this is a cat named cat, it can not bark like a dog
When we use sealed keywork, the Scala compiler will check the pattern match is exhaustive or not.
// if we miss a type to match
def returnAnimalSubtypeObject(x: Animal): Animal = x match {
case d: Dog => d
}
// it will show warning.
match may not be exhaustive.
[warn] It would fail on the following input: Cat(_, _)
[warn] def returnAnimalSubtypeObject(x: Animal): Animal = x match {
[warn] ^
[warn] one warning found
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Dima |
| Solution 3 |
