joescii
joescii

Reputation: 6533

Why does Scala not infer the type parameters when pattern matching with @

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

Answers (3)

regexp
regexp

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

goral
goral

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

kiritsuku
kiritsuku

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

Related Questions