Lightweight type erasure matching

Scala’s pattern matching is much touted as a killer feature, and generally easy and convenient to use, but due to type erasure one needs to be somewhat careful with typed collections. 

Check out the following bit of code:

1
2
3
4
5
6
scala> List( "You say goodbye" , "I say hello" ) match {
      |   case list : List[String] = > list foreach println
      | }
warning : there were unchecked warnings; re-run with -unchecked for details
You say goodbye
I say hello

It’s tempting to say, “this worked,” and move along, but consider the following similar code where we instead provide a list of Ints and add a matcher for that type:

1
2
3
4
5
6
scala> List( 27 , 35 , 82 ) match {
      |   case strList : List[String] = > println( "List of Strings" )
      |   case intList : List[Int] = > println( "List of Ints" )
      | }
warning : there were unchecked warnings; re-run with -unchecked for details
List of Strings

Damn, that’s not what we wanted at all. I guess we should see what that “unchecked warning” message is all about by restarting the interpreter with that option…

1
2
3
4
5
6
7
8
9
10
11
scala> List( 27 , 35 , 82 ) match {                                                 
      |   case strList : List[String] = > println( "List of Strings" )
      |   case intList : List[Int] = > println( "List of Ints" )
      | }
<console> : 7 : warning : non variable type -argument String in type pattern List[String] is unchecked since it is eliminated by erasure
          case strList : List[String] = > println( "List of Strings" )
                        ^
<console> : 8 : warning : non variable type -argument Int in type pattern List[Int] is unchecked since it is eliminated by erasure
          case intList : List[Int] = > println( "List of Ints" )
                        ^
List of Strings

Aha! Type erasure! You see, Scala is forced to do type erasure due to the fact that the Java Virtual Machine (JVM) isn’t aware of generics, which underlie typed collections such as the Lists we’re looking at here. As such, any type of List will match the “List[String]” case and we never reach the expected case for Int lists.

This specific topic is covered in a post over on Stack Overflow, with several excellent responses there as well as links to two workarounds.

However, my immediate needs in this area were limited in scope, only affecting a few lines of code and occurring within a single source file. I essentially wanted to send a List[String] to an Actor and have it handle this appropriately in a match block. I was reluctant to rely on a method that was termed “obscure” and “experimental”, and moreover wanted something lightweight that didn’t add a lot of code. Besides, it doesn’t seem like something like this should have to tap into the reflection libraries.

What I came up with was essentially wrapping List[String] inside of a single-argument case class, which could then be passed around and matched against:

1
case class StringListHolder(list : List[String])

So back to the original example, rewritten:

1
2
3
4
5
scala> StringListHolder(List( "You say goodbye" , "I say hello" )) match {
      |   case holder : StringListHolder = > holder.list foreach println
      | }
You say goodbye
I say hello

Exactly the output we want, no compiler warnings, and a class that by definition can’t hold anything other than a List of Strings. Sometimes it pays to keep it simple.

猜你喜欢

转载自blog.csdn.net/hany3000/article/details/78399082