Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala type erasure for pattern matching

I have been searching the forum and Google for answers to type erasure issues for Scala. However, I cannot find anything that answers my question.

I struggling with pattern matching on objects that match the type parameter of ParamClass. I need to pattern match on the type of incoming objects to the bar method. I have seen solutions such as

bar[X](a : X)(implicit m : Manifest[X])

which would solve my problem, but I cannot use this as the bar method is an overridden method. (Actually is the receive partial function in the Akka actor framework). The code is given below and should be self explanatory:

class ParamClass[A : Manifest] {
  def bar(x : Any) = x match {
    case a: A => println("Found A: " + a)
    case _ =>    println("No match: " + x)
  }
}

object ErasureIssue {
  def main(args: Array[String]) {
    val clz = new ParamClass[Int]
    clz.bar("faf")
    clz.bar(2.3)
    clz.bar(12)   // this should match, but does not
  }
}

ErasureIssue.main(null)

Any help on solving this issue is greatly appreciated. I'm using Scala 2.9.1, BTW.

-J

like image 800
pedesky Avatar asked Aug 08 '12 12:08

pedesky


2 Answers

In theory you could check in bar like this: x.getClass == implicitly[Manifest[A]].erasure, but that fails for primitive types such as Int for which the manifest correctly erases to Int, but bar is called with boxed type java.lang.Integer ... :-(

You could require A to be an AnyRef in order to get the boxed manifest:

class ParamClass[A <: AnyRef : Manifest] {
  def bar(x : Any) = x match {
    case _ if x.getClass == implicitly[Manifest[A]].erasure =>
      println("Found A: " + x.asInstanceOf[A])
    case _ => println("No match: " + x)
  }
}

object ErasureIssue {
  def main(args: Array[String]) {
    val clz = new ParamClass[Integer] // not pretty...
    clz.bar("faf")
    clz.bar(2.3)
    clz.bar(12)   // ok
  }
}

ErasureIssue.main(null)

Given your requirement to construct primitive arrays, you could store directly the boxed class, independently of the unboxed manifest:

object ParamClass {
  def apply[A](implicit mf: Manifest[A]) = {
    val clazz = mf match {
      case Manifest.Int => classOf[java.lang.Integer] // boxed!
      case Manifest.Boolean => classOf[java.lang.Boolean]
      case _ => mf.erasure
    }
    new ParamClass[A](clazz)
  }
}
class ParamClass[A] private[ParamClass](clazz: Class[_])(implicit mf: Manifest[A]) {
  def bar(x : Any) = x match {
    case _ if x.getClass == clazz =>
      println("Found A: " + x.asInstanceOf[A])
    case _ => println("No match: " + x)
  }

  def newArray(size: Int) = new Array[A](size)

  override def toString = "ParamClass[" + mf + "]"
}

val pi = ParamClass[Int]
pi.bar("faf")
pi.bar(12)
pi.newArray(4)

val ps = ParamClass[String]
ps.bar("faf")
ps.bar(12)
ps.newArray(4)
like image 97
0__ Avatar answered Sep 25 '22 05:09

0__


If you try to compile with -unchecked, you immediately get the warning.

test.scala:3: warning: abstract type A in type pattern A is unchecked since it is eliminated by erasure case a: A => println("Found A: " + a)

If you now want to go deeper, you can use scalac -print

[[syntax trees at end of cleanup]]// Scala source: test.scala
package <empty> {
  class ParamClass extends java.lang.Object with ScalaObject {
    def bar(x: java.lang.Object): Unit = {
      <synthetic> val temp1: java.lang.Object = x;
      if (temp1.$isInstanceOf[java.lang.Object]())
        {
          scala.this.Predef.println("Found A: ".+(temp1))
        }
      else
        {
          scala.this.Predef.println("No match: ".+(x))
        }
    };
    def this(implicit evidence$1: scala.reflect.Manifest): ParamClass = {
      ParamClass.super.this();
      ()
    }
  };
  final object ErasureIssue extends java.lang.Object with ScalaObject {
    def main(args: Array[java.lang.String]): Unit = {
      val clz: ParamClass = new ParamClass(reflect.this.Manifest.Int());
      clz.bar("faf");
      clz.bar(scala.Double.box(2.3));
      clz.bar(scala.Int.box(12))
    };
    def this(): object ErasureIssue = {
      ErasureIssue.super.this();
      ()
    }
  }
}

Now seeing this code you can see that your A has turned into a java.lang.Object, which cause all the parameters to match the clause

like image 28
Edmondo1984 Avatar answered Sep 24 '22 05:09

Edmondo1984