Pattern matching for abstracted case classes

228 views Asked by At

I'm trying to abstract case classes in a module using dependent method types and a nightly build of the compiler (2.10.0.r26005-b20111114020239). I found some inspiration from Miles Sabin' example.

I don't really understand what's wrong in the (self-contained) code below. The output depends on the order of the patterns in foo.

// afaik, the compiler doesn't not expose the unapply method
// for a companion object
trait Isomorphic[A, B] {
  def apply(x: A): B
  def unapply(x: B): Option[A]
}

// abstract module
trait Module {
  // 3 types with some contraints
  type X
  type Y <: X
  type Z <: X
  // and their "companion" objects
  def X: Isomorphic[Int, X]
  def Y: Isomorphic[X, Y]
  def Z: Isomorphic[Y, Z]
}

// an implementation relying on case classes
object ConcreteModule extends Module {
  sealed trait X { val i: Int = 42 }
  object X extends Isomorphic[Int, X] {
    def apply(_s: Int): X = new X { }
    def unapply(x: X): Option[Int] = Some(x.i)
  }
  case class Y(x: X) extends X
  // I guess the compiler could do that for me
  object Y extends Isomorphic[X, Y]
  case class Z(y: Y) extends X
  object Z extends Isomorphic[Y, Z]
}

object Main {
  def foo(t: Module)(x: t.X): Unit = {
    import t._
    // the output depends on the order of the first 3 lines
    // I'm not sure what's happening here...
    x match {
      // unchecked since it is eliminated by erasure
      case Y(_y) => println("y "+_y)
      // unchecked since it is eliminated by erasure
      case Z(_z) => println("z "+_z)
      // this one is fine
      case X(_x) => println("x "+_x)
      case xyz => println("xyz "+xyz)
    }
  }
  def bar(t: Module): Unit = {
    import t._
    val x: X = X(42)
    val y: Y = Y(x)
    val z: Z = Z(y)
    foo(t)(x)
    foo(t)(y)
    foo(t)(z)
  }
  def main(args: Array[String]) = {
    // call bar with the concrete module
    bar(ConcreteModule)
  }
}

Any idea?

1

There are 1 answers

0
Miles Sabin On BEST ANSWER

The warnings are correct and to be expected because, as viewed from within foo, Y and Z will both have been erased to their bounds, ie. X.

What's more surprising is that the presence of either the match against Y or the match against Z frustrate the match against X, ie. in this case,

def foo(t: Module)(x: t.X): Unit = {
  import t._
  // the output depends on the order of the first 3 lines
  // I'm not sure what's happening here...
  x match {
    // unchecked since it is eliminated by erasure
    // case Y(_y) => println("y "+_y)
    // unchecked since it is eliminated by erasure
    // case Z(_z) => println("z "+_z)
    // this one is fine
    case X(_x) => println("x "+_x)
    case xyz => println("xyz "+xyz)
  }
}

the result is,

x 42
x 42
x 42

which seems reasonable, whereas with one of the earlier matches restored,

def foo(t: Module)(x: t.X): Unit = {
  import t._
  // the output depends on the order of the first 3 lines
  // I'm not sure what's happening here...
  x match {
    // unchecked since it is eliminated by erasure
    case Y(_y) => println("y "+_y)
    // unchecked since it is eliminated by erasure
    // case Z(_z) => println("z "+_z)
    // this one is fine
    case X(_x) => println("x "+_x)
    case xyz => println("xyz "+xyz)
  }
}

the result is,

xyz AbstractMatch$ConcreteModule$X$$anon$1@3b58fa97
y AbstractMatch$ConcreteModule$X$$anon$1@3b58fa97
xyz Z(Y(AbstractMatch$ConcreteModule$X$$anon$1@3b58fa97))

which doesn't: I can't see any good reason why the additional case would cause xyz to be chosen over X, so I think you've run into a bug in the pattern matcher. I suggest you search the Scala JIRA for similar issues, and if you can't find one, open a ticket with a minimized reproducing example extracted from the above.

To be honest in the second example above, I would have expected the Y case to have been chosen in all three instances thanks to Y being erased to X and the Y case preceeding the X case in the match expression. But we're in unchecked territory here and I'm not 100% confident of my intuitions.