Reputation: 6533
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?
Upvotes: 3
Views: 1394
Reputation: 766
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)
Upvotes: 2
Reputation: 1265
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
Upvotes: 0
Reputation: 53358
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.
Upvotes: 5