Riccardo
Riccardo

Reputation: 413

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?

Upvotes: 0

Views: 1103

Answers (3)

Dima
Dima

Reputation: 40500

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() ...

Upvotes: 1

counter2015
counter2015

Reputation: 1052

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

Upvotes: 0

Levi Ramsey
Levi Ramsey

Reputation: 20551

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()

Upvotes: 1

Related Questions