Search code examples
scalaakkatype-erasurescala-reflect

Preserving type arguments in Akka receive


This question has kind of been answered by Roland Kuhn in this post, however, despite several comments asking for detail, he didn't bother to share the complete answer.

Here's what I want to do: I have a wrapper class case class Event[T](t: T) of which I send instances to an Akka actor. In the receive method of that actor, I then want to distinguish between Event[Int] and Event[String], which obviously isn't so simple due to type erasure.

What Roland Kuhn shares in the mentioned post is that "there is exactly one way to do it", that is, embodying the type information within the message. So I did this:

case class Event[T](t: T)(implicit val ct: ClassTag[T])

Even though asked by different people to provide it, Roland Kuhn does not say what to actually do within the receive method then. Here's what I tried.

def receive = {
  case e: Event =>
    if (e.ct.runtimeClass == classOf[Int])
      println("Got an Event[Int]!")
    else if (e.ct.runtimeClass == classOf[String])
      println("Got an Event[String]!")
    else
      println("Got some other Event!")
  case _ =>
    println("Got no Event at all!")
}

This is the best I could come up with as it's hard to wrap one's head around Scala's reflection jungle. It's not compiling, though:

value ct is not a member of Any
else if (e.ct.runtimeClass == classOf[String])
           ^

Thus, I am asking specifically about what the receive method should look like.


Solution

  • After fixing the error Event takes type parameters:

    def receive = {
      case e: Event[_] =>
        if (e.ct.runtimeClass == classOf[Int])
          println("Got an Event[Int]!")
        else if (e.ct.runtimeClass == classOf[String])
          println("Got an Event[String]!")
        else
          println("Got some other Event!")
      case _ =>
        println("Got no Event at all!")
    }
    

    the code compiles. It can be slightly simplified by not looking inside the ClassTags (of course, the implementation of ClassTag#equals is going to compare the classes):

    import scala.reflect.{ClassTag, classTag}
    
    def receive = {
      case e: Event[_] =>
        if (e.ct == ClassTag.Int) // or classTag[Int]
          println("Got an Event[Int]!")
        else if (e.ct == classTag[String])
          println("Got an Event[String]!")
        else
          println("Got some other Event!")
      case _ =>
        println("Got no Event at all!")
    }