Trying to extract the TypeTag of a Sequence of classes that extend a trait with different generic type parameters

Viewed 19

The following code example shows the core of my question:

// This is the base trait that the classes are extending
trait Operation[T] {
  def operate(x: T): T
}

// Here are 2 case classes that exist for the sole purpose of being
// the generic type for the classes I'm making
case class CaseClass1(field1_1: String, field1_2: String, field1_3: String)
case class CaseClass2(field2_1: String, field2_2: String, field2_3: String)

// These are the 2 classes that extend my basic trait, implementing the operate
// method with some kind of logic
class FillField1 extends Operation[CaseClass1] {
  def operate(input: CaseClass1): CaseClass1 = input.copy(field1_3 = "haha")
}
class FillField2 extends Operation[CaseClass2] {
  def operate(input: CaseClass2): CaseClass2 = input.copy(field2_2 = "hoho")
}

import scala.reflect.runtime.universe._
// This is a function that prints out the typetag information currently available
def someFunc[T: TypeTag](x: Operation[T]): Unit = {
  println(s"TypeTag is: ${typeOf[T]}")
}

// Here I'm creating a sequence of Operations, but they can have any generic
// type parameter
val someSeq: Seq[Operation[_]] = Seq(new FillField1, new FillField2)

someSeq.map(someFunc(_))
/*
  Output:
  TypeTag is: _$1
  TypeTag is: _$1
*/

someFunc(new FillField1)
/*
  Output:
  TypeTag is: CaseClass1
*/

As you can see, when I call someFunc(new fillField1) I can properly find my typetag at runtime. But when I'm using someSeq, which is a Sequence that can contain multiple types of classes I can't get the typetag I need at runtime. Is this because you lose that information at runtime?

How can I get the proper typetag at runtime? So how could I get as output TypeTag is: CustomClass1 and TypeTag is: CustomClass2 when I'm using that Seq[Operation[_]]?

I'm working on an Apache Spark project where we have a structure similar to this and when I'm using that sequence I'm getting an issue that the TypeTag points to an unknown class, _$10 (or whatever name the compiler made for my typetag), instead of the actual TypeTag which would be CustomClass1 or CustomClass2...

1 Answers

What TypeTag mostly does is not runtime reflection but persisting some information (a type) from compile time to runtime.

Seq is a homogeneous collection (i.e. all its elements have the same type). In Seq(new FillField1, new FillField2) both elements have type Operation[_]. So when someFunc is applied T is inferred to be _ aka _$1 (i.e. unknown argument of existential type Operation[_]).

So one option is to use a heterogeneous collection (HList). Then the elements can have different types, these types can be captured from compile time to runtime, the types can be handled at runtime

import shapeless.{HNil, Poly1}

object someFuncPoly extends Poly1 {
  implicit def cse[T: TypeTag, O](implicit ev: O <:< Operation[T]): Case.Aux[O, Unit] =
    at(x => someFunc(x))
}

def someFunc[T: TypeTag](x: Operation[T]): Unit = println(s"Type is: ${typeOf[T]}")

(new FillField1 :: new FillField2 :: HNil).map(someFuncPoly)
//Type is: CaseClass1
//Type is: CaseClass2

Another option is to use runtime reflection (i.e. what TypeTag doesn't do)

import scala.reflect.runtime.universe._
import scala.reflect.runtime
val runtimeMirror = runtime.currentMirror

def someFunc(x: Operation[_]): Unit = {
  val xSymbol = runtimeMirror.classSymbol(x.getClass)
  val operationSymbol = xSymbol.baseClasses(1)// or just typeOf[Operation[_]].typeSymbol if you know Operation statically
  val extendee = xSymbol.typeSignature/*toType*/.baseType(operationSymbol)
  println(s"Type is: ${extendee.typeArgs.head}")
}

someSeq.map(someFunc(_))
//Type is: CaseClass1
//Type is: CaseClass2

Another implementation is

def someFunc(x: Operation[_]): Unit = {
  val xSymbol = runtimeMirror.classSymbol(x.getClass)
  val operationSymbol = xSymbol.baseClasses(1).asClass
  val operationParamType = operationSymbol.typeParams(0).asType.toType
  println(s"Type is: ${operationParamType.asSeenFrom(xSymbol.toType, operationSymbol)}")
}
Related