Cómo encontrar el tipo de datos del parámetro de clase en tiempo de ejecución en Scala

Nov 29 2020
import scala.reflect.runtime.universe
import scala.reflect.runtime.universe._

def getType[T: TypeTag](obj: T) = typeOf[T]

case class Thing(
  val id: Int,
  var name: String
)
val thing = Thing(1, "Apple")

val dataType = getType(thing).decl(TermName("id")).asTerm.typeSignature

dataType match {
 case t if t =:= typeOf[Int] => println("I am Int")
 case t if t =:= typeOf[String] => println("String, Do some stuff")
 case _ => println("Absurd")
}

No es capaz de digerir por qué el resultado es en Absurdlugar de I am int?

Mi objetivo es conocer el tipo de datos del parámetro de clase en tiempo de ejecución y hacer coincidirlo con tipos predefinidos.

Respuestas

4 DmytroMitin Nov 29 2020 at 17:20

Ambos actuales dataTypey typeOf[Int]están impresos como, Intpero si lo hace showRaw, verá por qué no coinciden

showRaw(dataType) // NullaryMethodType(TypeRef(ThisType(scala), scala.Int, List()))
showRaw(typeOf[Int]) // TypeRef(ThisType(scala), scala.Int, List())

El caso es que solo el tipo Inty el tipo de método nular que regresa Intson tipos diferentes.

Intenta agregar .resultType

val dataType = getType(thing).decl(TermName("id")).asTerm.typeSignature.resultType

dataType match {
  case t if t =:= typeOf[Int] => println("I am Int")
  case t if t =:= typeOf[String] => println("String, Do some stuff")
  case _ => println("Absurd")
} // I am Int

También vale la pena mencionar que .decl(TermName("id"))devuelve el símbolo de obtención, es .decl(TermName("id "))(con un espacio en blanco) el que devuelve el símbolo de campo. Entonces, alternativamente, puede hacerlo con un espacio en blanco en el nombre del símbolo y sin.resultType

val dataType = getType(thing).decl(TermName("id ")).asTerm.typeSignature

Agregaré a la respuesta de @TomerShetah que si el objetivo es "hacer coincidir el patrón" con todos los campos de una clase de caso, esto también se puede hacer en tiempo de compilación (principalmente) con Shapeless :

import shapeless.Poly1
import shapeless.syntax.std.product._

object printTypes extends Poly1 {
  implicit val int: Case.Aux[Int, Unit] = at(t => println(s"I am Int: $t")) implicit val string: Case.Aux[String, Unit] = at(t => println(s"String, Do some stuff: $t"))
  implicit def default[V]: Case.Aux[V, Unit] = at(t => println(s"Absurd: $t"))
}
  
thing.toHList.map(printTypes)
// I am Int: 1
// String, Do some stuff: Apple

https://scastie.scala-lang.org/DmytroMitin/N4Idk4KcRumQJZE2CHC0yQ

1 TomerShetah Nov 30 2020 at 11:28

La respuesta de @Dmytrio es una gran explicación de por qué la reflexión no funcionó como esperabas.

Puedo entender por su pregunta, que lo que está tratando de hacer, es que el patrón coincide con todas las variables que tiene en una clase de caso. Considere hacerlo de la siguiente manera:

case class Thing(id: Int, name: String)
val thing = Thing(1, "Apple")

thing.productIterator.foreach {
  case t: Int => println(s"I am Int: $t") case t: String => println(s"String, Do some stuff: $t")
  case t => println(s"Absurd: $t")
} 

El código se ejecuta en Scastie .