3
votes

I'm using Scala 2.10.4 with akka 2.3.4. I ran into a problem where type inference is not behaving the way I expected.

The code below illustrates an example of what I am experiencing. I have a case class which wraps messages with an id named MyMessage. It is parameterized with the type of the message. Then I have a payload named MyPayload which contains a String.

Within an actor (here I'm just using a regular object named MyObject since the problem isn't particular to akka) I am pattern matching and calling a function that operates on my payload type MyPayload.

package so

case class MyMessage[T](id:Long, payload:T)
case class MyPayload(s:String)

object MyObject {
  def receive:PartialFunction[Any, Unit] = {
    case m @ MyMessage(id, MyPayload(s)) =>

      // Doesn't compile
      processPayload(m)

      // Compiles
      processPayload(MyMessage(id, MyPayload(s)))
  }

  def processPayload(m:MyMessage[MyPayload]) = {
    println(m)
  }
}

For reasons I don't understand, pattern patching with @ and an unapplied case class doesn't infer the type parameter of MyMessage[T]. In the code above, I would have expected that m would have type MyMessage[MyPayload]. However, when I compile, it believes that the type is MyMessage[Any].

[error] PatternMatch.scala:9: type mismatch;
[error]  found   : so.MyMessage[Any]
[error]  required: so.MyMessage[so.MyPayload]
[error] Note: Any >: so.MyPayload, but class MyMessage is invariant in type T.
[error] You may wish to define T as -T instead. (SLS 4.5)
[error]       processPayload(m)
[error]                      ^
[error] one error found
[error] (compile:compile) Compilation failed
[error] Total time: 1 s, completed Aug 19, 2014 12:08:04 PM

Is this expected behavior? If so, what have I misunderstood about type inference in Scala?

3
You would need to create PartialFunction[MyMessage[MyPayload], Unit]. but then, what's the point? :)goral
Interesting... that does work. Of course, you're also right that there is no point. I purposefully typed it as PartialFunction[Any, Unit] because that is what an actor's receive:Receive is typed as.joescii

3 Answers

5
votes

You can't extract type parameters in pattern matching - it is a limitation of the current implementation and/or the runtime. Because type parameters are erased at runtime it would require a lot of overhead to restore them - therefore you can't use an unapply method that takes an type parameter in a pattern match.

In your case it looks simpler, because the compiler could just infer the type from the extractor arguments. But generally it isn't that easy and probably the reason why it doesn't even work in your case.

See this long living ticket about the issue.

2
votes

The problem you ran into is type erasure

The JVM doesn't know anything about generic types during runtime, see:

How do I get around type erasure on Scala? Or, why can't I get the type parameter of my collections?

To make it compile, you have to tell the compile explizitly which type you expect

 def receive:PartialFunction[Any, Unit] = {
    case message: MyMessage[MyPayload] =>
      processPayload(message)
  }

Warning: This will still match any MyMessage[_] and can cause runtime exceptions.

To ensure the type during runtime, you need to use TypeTags (see the link above)

0
votes

Strangely enough it also work for :

def receive: PartialFunction[Any, Unit] = {
 case m : MyMessage[MyPayload] => processPayload(m)
}

scala> MyObject.receive.isDefinedAt(MyMessage(12L, MyPayload("string")))
res9: Boolean = true