Reputation: 849
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?
Upvotes: 3
Views: 229
Reputation: 23046
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.
Upvotes: 1