Search code examples
scalatype-erasure

Erasure of type fields


abstract class Handler {
  type Message

  def handleAny(msg: Any) {
    if (msg.isInstanceOf[Message]) handle(msg.asInstanceOf[Message]) // fix me!
  }

  def handle(msg: Message)
}

class StringHandler extends Handler {
  override type Message = String

  def handle(msg: Message) { println(s"handling: $msg") }
}

val h = new StringHandler
h.handleAny("ahoj")
h.handleAny(true)

warning: abstract type Handler.this.Message is unchecked since it is
 eliminated by erasure
                if(msg.isInstanceOf[Message]) handle(msg.asInstanceOf[Message])
                                   ^
one warning found
handling: ahoj
java.lang.ClassCastException: java.lang.Boolean cannot be cast to java.lang.String
        at Main$$anon$1$StringHandler.handle(typeProblem.scala:11)
        at Main$$anon$1$Handler.handleAny(typeProblem.scala:5)

How to change the snippet (without changing interface or replacing type field with type parameter) to work as expected, which is to handle only messages specified by the type field?

I'm looking for a solution which can be applied to the parent abstract class rather than polluting all children.

I also tried to use a match and it behaves exactly same. Or is this kind of thing impossible with type fields (I thought they are more powerful than type parameters)?


Solution

  • One usually uses a match and in your case, as you do not want to add any type parameters you will have to specify a ClassTag (I think).

    Something along the lines of:

    import scala.reflect.ClassTag
    
    abstract class Handler {
      type Message <: Any
      implicit val tag: ClassTag[Message]
    
      def handleAny(msg: Any): Unit = {
        msg match {
          case tag(message) =>
            handle(message)
          case _ =>
            throw new IllegalArgumentException(s"Argmument MUST BE a 'Message' but $msg is not!")
        }
      }
    
      def handle(msg: Message): Unit
    }
    
    object StringHandler extends Handler {
      override type Message = String
      // lazy is important here!
      implicit lazy val tag: ClassTag[Message] = ClassTag(classOf[String])
    
      def handle(msg: Message): Unit =  { println(s"handling: $msg") }
    }
    
    
    StringHandler.handleAny("ahoj")
    StringHandler.handleAny(true)