Scala generic method - No ClassTag available for T - when using Collection
Asked Answered
L

1

0

I want to leverage Scala reflection to implicitly pass a ClassTag.

There are plenty solutions on StackOverflow on how to accomplish this.

import scala.reflect.ClassTag

// animal input
trait AnimalInput {}
case class DogInput() extends AnimalInput
case class CatInput() extends AnimalInput

// animals
trait Animal[T <: AnimalInput] {
  def getInput(implicit ct: ClassTag[T]): Class[T] = {
    ct.runtimeClass.asInstanceOf[Class[T]]
  }
}
object Dog extends Animal[DogInput] {}
object Cat extends Animal[CatInput] {}

I can test that this is working well:

println(Dog.getInput) // Success: "DogInput"
println(Cat.getInput) // Success: "CatInput"

The problem is, the second I reference these objects in any collection, I run into trouble:

// Failure: "No ClassTag available for animal.T"
List(Dog, Cat).foreach(animal => println(animal.getInput)) 

I think I understand why this is happening but I'm not sure how to work around it.

Thank you in advance for your help!

Liberati answered 25/9, 2022 at 18:8 Comment(4)
You could store the classtag. But what do you really want to achieve with all this? Sounds like XY problem.Ballerina
This code compiles fineSchrader
@AdamBerry Please see updates with magnet and with abstract classWoorali
@Schrader 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
W
2

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
Woorali answered 25/9, 2022 at 20:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.