How to get the type of a type parameter?

How can we extract the name/type of “P” preferrably in compile-time?

Given the following traits:
trait Plugin
trait NetworkPlugin extends Plugin
trait StoragePlugin extends Plugin

and the event class:
case class Event[P <: Plugin](
// How do we know the name give the plugin type (P)?
val eventType: Class[P] = ???
val eventName: String = ???
)

val networkEvent = EventNetworkPlugin
val storageEvent = EventStoragePlugin

It sounds like you’re skirting dangerously close to “you can’t do that, because of erasure” (which is a big and important topic when you’re trying to do things like this to generics). Suffice it to say, there are a lot of things you can’t just casually do with type parameters.

That said, you can get at least some information about P by using the ClassTag type class, which creates a synthetic implicit parameter with information about the specified type. It’s imprecise (which can matter when trying to do more-sophisticated things), but if all you need is the Class object and name of an ordinary trait, it will suffice. I use it moderately often to do things like this.

Here’s a fully-worked version of what I think you’re trying to do here:

import scala.reflect.ClassTag
trait Plugin
trait NetworkPlugin extends Plugin
trait StoragePlugin extends Plugin

case class Event[P <: Plugin : ClassTag]() {
  // How do we know the name give the plugin type (P)?
  val eventType: Class[?] = summon[ClassTag[P]].runtimeClass
  val eventName: String = eventType.getSimpleName()
}

val networkEvent = Event[NetworkPlugin]()
val storageEvent = Event[StoragePlugin]()

println(networkEvent.eventName)
println(storageEvent.eventName)
3 Likes

Thank you very much for quick and good response. Indeed, it is exactly what I needed and works perfectly in my case.

You can create an array whose element types are designated by the type variable:

import scala.reflect.ClassTag

object TypeErasureTag:
  //  No ClassTag available for T
  //  def createArray[T](length: Int, element: T) = new Array[T](length)
  def createArray[T: ClassTag](length: Int, element: T): Array[T] =
    val arr = new Array[T](length)
    if length > 0 then arr(0) = element
    arr

  @main def runTypeErasureTag():Unit =
    println(createArray(1, "howdy").toList.toString)

I am posting another solution that seems to be more simple and more reliable than the previous solutions:

trait Plugin
case class PluginEvent[T <: Plugin](clazz: Class[T])

trait NetworkPlugin extends Plugin
trait FirewallPlugin extends Plugin

class Listener {
  def isInterested(message: Any): Boolean = message match {
    case PluginEvent(clazz) => {
      if (clazz == classOf[NetworkPlugin]) {
        println("Found NetworkPlugin clazz:" + clazz)
        true
      } else if (clazz == classOf[FirewallPlugin]) {
        println("Found FirewallPlugin clazz:" + clazz)
        true
      } else {
        println("Only partial match")
        false
      }
    }
    case _ => {
      println("No match at all")
      false
    }
  }
}

@main
def main(): Unit = {

  val listener = new Listener()

  val networkPluginEvent = PluginEvent(classOf[NetworkPlugin])
  listener.isInterested(networkPluginEvent)
  println("-" * 80)
  val firewallPluginEvent = PluginEvent(classOf[FirewallPlugin])
  listener.isInterested(firewallPluginEvent)

}

results:

Found NetworkPlugin clazz:interface NetworkPlugin
--------------------------------------------------------------------------------
Found FirewallPlugin clazz:interface FirewallPlugin