scalapattern-matchingakka-fsm

How to explain these pattern matching examples?


I wrote some events in FSM, and discovered something I can not explain when pattern matching. I thought the following was completely legal, that is that I can send this actor either a message which is a vector[A] or vector[B].

when(State) {
    case Event(content: Vector[A], _) => {
      println("matched A")
      stay
   }
   case Event(content: Vector[B], _) => {
     println("matched B")
     stay
  }  
}

However, if I send the actor a vector[B] message it leads to

java.lang.ClassCastException: B cannot be cast to A

So basically it tries to match the first event eventhough the next would match.

I tried to make an even simpler pattern match example;

object Pattern extends App {
    val n = Vector(1,2,3)
    val s = Vector("S", "S", "S")
    n match{
       case e:Vector[String] => println("matched string")
       case v:Vector[Int] => println("matched int")
   }

}

This is actually not legal;

Error:(8, 12) pattern type is incompatible with expected type;
found   : Vector[String]
required: scala.collection.immutable.Vector[Int]
case e:Vector[String] => println("matched string")

However, I am allowed to run my code if I do the following cast;

object Pattern extends App {
  val n = Vector(1,2,3).asInstanceOf[Vector[Any]]
  val s = Vector("S", "S", "S")
  n match{
    case e:Vector[String] => println(n(0).getClass)
    case v:Vector[Int] => println("matched int")
  }
}

The thing I find strange then is that I apparently say that Any could match a String, but the print is java.lang.Integer. So should I think of it as I have an vector[Int] that I say is a Vector[Any], since Vector[Any] could be a Vector[String] it matches that pattern, and again since it really is a vector[Int] I mask as Vector[Any] the print is fine too.

Could someone explain these pattern matching observations?

and how should I set up the messages so my state can handle both messages of Vector[A] and Vector[B]?


Solution

  • Due to type erasure of jvm type information is lost at runtime this kind of pattern matching (pattern matching with higher kinded types) is not supported directly.

    Here are the ways to get around this problem

    Instead I recommend you to wrap the vector in another container.

    sealed trait Vectors
    
    case class VectorString(vs: Vector[String]) extends Vectors
    
    case class VectorInt(vi: Vector[Int]) extends Vectors
    
    def doStuff(v: Vectors) = v match {
     case VectorString(vs) => //be sure that vs is Vector[String]
     case VectorInt(vi) => 
    }
    

    Ways to pattern match generic types in Scala

    Using TypeTag

    import scala.reflect.runtime.universe._
    
    def handle[A: TypeTag](a: A): Unit =
      typeOf[A] match {
        case t if t =:= typeOf[List[String]] =>
          // list is a string list
          val r = a.asInstanceOf[List[String]].map(_.length).sum
          println("strings: " + r)
    
        case t if t =:= typeOf[List[Int]] =>
          // list is an int list
          val r = a.asInstanceOf[List[Int]].sum
          println("ints: " + r)
    
        case _ => // ignore rest
      }
    
    val ints: List[Int] = Nil
    
    handle(List("hello", "world")) // output: "strings: 10"
    handle(List(1, 2, 3))          // output: "ints: 6"
    handle(ints)                   // output: "ints: 0" it works!