is it possible to overcome type erasure in akka receive?

1.1k Views Asked by At

I have a trait and implementing akka actor that can't properly differentiate the types of incoming messages due to type erasure, and so the first case is matching all messages.

I'm using scala 2.10.x, and from reading many other answers I know that it is possible to recover type information inside a trait using TypeTag or ClassTag, but I can't figure out how to apply it (if it is possible) within the akka receive.

My (very simplified) example is as follows. Is it possible properly match the generic types?

package com.ebay.box.canada.batch.jobs.siteMap

import akka.actor.Actor
import akka.actor.ActorSelection
import akka.actor.Actor.Receive
import scala.reflect.ClassTag


trait MessageProcessor[A,B] {
  this: Actor =>

  val destA: ActorSelection
  val destB: ActorSelection

  def processA(a: A): A
  def processB(a: B): B

  def receive: PartialFunction[Any,Unit] = {
    case a: A =>
      destA ! processA(a)
    case b: B =>
      destB ! processB(b)
  }
}

class StringIntProcessor(val destA: ActorSelection, val destB: ActorSelection) extends MessageProcessor[String,Int] with Actor {
  def processA(a: String) = { a + "1" }
  def processB(b: Int) = { b + 1 }
}
1

There are 1 best solutions below

6
On

I don't think you can get at TypeTag[A] or ClassTag[A] in your trait -- type tags/class tags are always part of the implicit argument list to a method call. You might be able to use an abstract class instead, with implicit constructor arguments:

import scala.reflect.runtime.universe._

abstract class MessageProcessor[A,B]()(implicit cta: ClassTag[A], ctb: ClassTag[B]) {
   def receive = {
     case a: Any if a.getClass() == cta.runtimeClass =>
       process(a.asInstanceOf[A])
     ...
   }
   ...
}

(Not tested!)

Supposing that you can change the code that sends the message, can I suggest the following design instead? MessageProcessor is now a typeclass, so that you can add any number of message types. By sending a closure as the message, you can smuggle any amount of context into the call site.

class MessageReceiver extends Actor {
  def receive = {
    case fn: Function0[Unit] =>
      fn()
  }
}

trait MessageProcessor[A] {
  val dest: ActorSelection
   def process(a: A): A
}

object Processors {

  implicit object StringProcessor extends MessageProcessor[String] {
    val dest: ActorSelection = Wellknown.stringDest
    def process(a: String): String = a + "1"
  }

  implicit object IntProcessor extends MessageProcessor[Int] {
    val dest: ActorSelection = Wellknown.intDest
    def process(a: Int): Int = a + 1
  }

  def sendMessage[A](msg: A)(implicit ev:[MessageProcessor[A]]): Unit = {
    val block: Function0[Unit] = { () =>
      ev.dest ! ev.process(msg)
    }

    val msgDest = system.actorOf[Props[MessageReceiver]], "msgDest")
    msgDest ! block
  }
}

(Also not tested!)