J-W
J-W

Reputation: 418

scala type erasure - matching case class

I was reading about type erasure, here

case class Thing[T](value: T)

def processThing(thing: Thing[_]) = {
  thing match {
    case Thing(value: Int) => "Thing of int"         //isn't thing Thing[_] anymore?
    case Thing(value: String) => "Thing of string"   //isn't thing Thing[_] anymore?
    case Thing(value: Seq[Int]) => "Thing of Seq[Int]"         //value=Seq[_] really
    case Thing(value: Seq[String]) => "Thing of Seq[String]"   //value=Seq[_] really
    case _ => "Thing of something else"
  }
}
println(processThing(Thing(Seq(1,2,3))))          //type erased, I get it
println(processThing(Thing(Seq("hello", "yo"))))  //type erased, I get it
println(processThing(Thing(1)))                   //why is this working?
println(processThing(Thing("hello")))             //why is this working?

I understand why Seq[Int] and Seq[String] are not identified correctly, at runtime both are seen like Seq[Object].

However, I do not understand why the first two examples ARE working: why Thing[Int] and Thing[String], both being Thing[T] are NOT having the same problem that Seq[T] has...

Why Thing[Seq[T]] is type erased but Thing[T] (T=Int, String) is not?

Can anybody explain what is going on here? Thanks

Upvotes: 1

Views: 446

Answers (2)

You are right that at runtime both Thing(1) and Thing("Hello") will erase to the same class Thing.

Thus, if you do something like this:

thing match {
  case _: Thing[Int] => foo
  case _: Thing[String] => bar
}

You would see the behavior you expected.

However, your pattern match is doing something different, it extracts the value inside thing and then performs a class check on that. The class information of the value is preserved by itself so that is why you can distinguish between Int, String and Seq, but you can't see what was the type parameter of the Seq
But, you may try to check the first element of the Seq ... but that still won't be enough, since the first element may be a Dog and the second a Cat because it was a Seq[Animal] and that check is even more unsafe than the previous ones since the Seq may be empty.

Upvotes: 5

Sean Vieira
Sean Vieira

Reputation: 159905

Consider the following Java code:

class Foo {
  <T> void bar(T whatIsIt) {
    System.out.println("It is a: " + whatIsIt.getClass().getName());
  }

  public static void main() {
    Foo f = new Foo();
    f.bar(123);
    f.bar("Hello world");
    f.bar(f);
    f.bar(new ArrayList<String>());
  }
}

At runtime there exists only one method Foo#bar(Object). But the object that is passed in as whatIsIt still has a pointer to whatever class it actually is.

Scala has access to the same metadata that Java does. So when it builds the bytecode for processThing it can insert value instanceOf Integer - and that's what it does:

scala> :javap processThing

// ... snip ...

  public java.lang.String processThing($line3.$read$$iw$Thing<?>);
    descriptor: (L$line3/$read$$iw$Thing;)Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=8, args_size=2
         0: aload_1
         1: astore_3
         2: aload_3
         3: ifnull        29
         6: aload_3
         7: invokevirtual #37                 // Method $line3/$read$$iw$Thing.value:()Ljava/lang/Object;
        10: astore        4
        12: aload         4
        14: instanceof    #39                 // class java/lang/Integer
        17: ifeq          26
        20: ldc           #41                 // String Thing of int

// ... snip ...

Upvotes: 2

Related Questions