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
...
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)}")
}
One more option is magnet pattern (1 2 3 4 5 6 7)
trait Magnet[T] {
def someFunc: Unit
}
import scala.language.implicitConversions
implicit def operationToMagnet[T: TypeTag](x: Operation[T]): Magnet[T] = new Magnet[T] {
override def someFunc: Unit = println(s"TypeTag is: ${typeOf[T]}")
}
def someFunc[T: TypeTag](x: Operation[T]): Unit = (x: Magnet[T]).someFunc
Seq[Magnet[_]](new FillField1, new FillField2).map(_.someFunc)
// TypeTag is: CaseClass1
// TypeTag is: CaseClass2
Alternatively you can move someFunc
inside Operation
, move TypeTag
from the method to the class and make Operation
an abstract class
abstract class Operation[T: TypeTag] {
def operate(x: T): T
def someFunc: Unit = {
println(s"TypeTag is: ${typeOf[T]}")
}
}
(new FillField1).someFunc //TypeTag is: CaseClass1
(new FillField2).someFunc //TypeTag is: CaseClass2
someSeq.map(_.someFunc)
//TypeTag is: CaseClass1
//TypeTag is: CaseClass2