Actually, I can't reproduce No ClassTag available for animal.T
. Your code compiles and runs in Scala 2.13.9:
https://scastie.scala-lang.org/DmytroMitin/lonnNg0fR1qb4Lg7ChDBqg
Maybe by failure you meant that for collection you don't receive classes DogInput
, CatInput
.
Actually, I managed to reproduce No ClassTag available for animal.T
for type member T <: AnimalInput
rather than type parameter: https://scastie.scala-lang.org/v79Y37eDRXuoauWA9b8uhQ
This question is very close to recent question Trying to extract the TypeTag of a Sequence of classes that extend a trait with different generic type parameters
See the reasons there.
Either use a heterogeneous collection
object classPoly extends Poly1 {
implicit def cse[T <: AnimalInput : ClassTag, A](implicit
ev: A <:< Animal[T]
): Case.Aux[A, Class[T]] =
at(_ => classTag[T].runtimeClass.asInstanceOf[Class[T]])
}
(Dog :: Cat :: HNil).map(classPoly).toList.foreach(println)
// class DogInput
// class CatInput
or use runtime reflection
trait Animal[T <: AnimalInput] {
def getInput: Class[T] = {
val classSymbol = runtimeMirror.classSymbol(this.getClass)
val animalSymbol = typeOf[Animal[_]].typeSymbol
val extendeeType = classSymbol.typeSignature.baseType(animalSymbol)
val extenderSymbol = extendeeType.typeArgs.head.typeSymbol.asClass
runtimeMirror.runtimeClass(extenderSymbol).asInstanceOf[Class[T]]
}
}
List(Dog, Cat).foreach(animal => println(animal.getInput))
// class DogInput
// class CatInput
The easiest is
trait Animal[T <: AnimalInput] {
def getInput: Class[T]
}
object Dog extends Animal[DogInput] {
val getInput = classOf[DogInput]
}
object Cat extends Animal[CatInput] {
val getInput = classOf[CatInput]
}
One more option is magnet pattern (1 2 3 4 5 6)
trait AnimalMagnet[T <: AnimalInput] {
def getInput: Class[T]
}
import scala.language.implicitConversions
implicit def animalToMagnet[A, T <: AnimalInput : ClassTag](a: A)(implicit
ev: A <:< Animal[T]
): AnimalMagnet[T] = new AnimalMagnet[T] {
override def getInput: Class[T] = classTag[T].runtimeClass.asInstanceOf[Class[T]]
}
List[AnimalMagnet[_]](Dog, Cat).foreach(animal => println(animal.getInput))
//class DogInput
//class CatInput
Also you can move ClassTag
implicit from the method to the trait (and make the trait an abstract class)
abstract class Animal[T <: AnimalInput](implicit ct: ClassTag[T]) {
def getInput: Class[T] = ct.runtimeClass.asInstanceOf[Class[T]]
}
List(Dog, Cat).foreach(animal => println(animal.getInput))
// class DogInput
// class CatInput
No ClassTag available for animal.T
would be for type member rather then type parameter scastie.scala-lang.org/v79Y37eDRXuoauWA9b8uhQ OP's code compiled but worked not as he wanted. – Woorali