Scala abstract types in classes within objects

2019-03-03 17:53发布

问题:

If I do this:

object Parent {
    class Inner extends Testable { type Self <: Inner }
    def inner = new Inner()
}

object Child {
    class Inner extends Parent.Inner { type Self <: Inner }
    def inner = new Inner()
}

trait Testable {
    type Self
    def test[T <: Self] = {}
}

object Main {
    // this works
    val p: Parent.Inner = Child.inner
    // this doesn't
    val h = Parent.inner
    h.test[Child.Inner]
}

I get this error:

error: type arguments [Child.Inner] do not conform to method test's type parameter bounds [T <: Main.h.Self]
    h.test[Child.Inner]

Why does this error when I my Self type is Parent.Inner and Child.Inner <: Parent.Inner?


And if I change type Self <: Inner to type Self = Inner and then override type Self = Inner, I get this error:

overriding type Self in class Inner, which equals Parent.Inner;
 type Self has incompatible type
    class Inner extends Parent.Inner { override type Self = Inner }

回答1:

This is a problem with path dependent types.

The test method of object h does not expect, as you would assume, a subtype of Parent.Inner. It expects a subtype of h.Self which is a slightly different type. Even though Child.Inner is a subtype of Parent.Inner, it is not a subtype of h.Self and that's why the compiler complains.

The problem with type members is that they are path dependent - they're bound to their enclosing instance and scalac will not allow you to pass type member of one instance where type member of another instance is expected. Child.Inner is not bound to any instance at all and will also be rejected.

Why is this needed? Look at this very similar code:

object Main {
  class C extends Child.Inner { type Self = C }

  val h: Parent.Inner = new C
  h.test[Child.Inner]
}

When looking at types, this code is exactly the same as yours (in particular, the type of h is exactly the same). But this code is clearly incorrect, because h.Self is actually C and Child.Inner is not a subtype of C. That's why scalac correctly rejects it.

You would expect that in your snippet scalac should remember that the type of h is exactly Parent.Inner, but unfortunately it doesn't keep that information. It only knows that h is some subtype of Parent.Inner.



回答2:

It appears that you are thinking of def inner = new Inner() as a field instead of thinking of it as a val. The difference is that a value is determined at the time a class is loaded, while the value using the def keyword is determined at run-time. This may not be exactly what you are looking for, as it answers another question, but there seems to be something funky going on here.

Since I am curious myself:

val p : Parent.Inner = Child.inner

This is obviously true, with this line:

class Inner extends Parent.Inner

however if we do:

val h = Parent.inner h.test[Parent.Inner]

does not work either with the following error:

type arguments [Parent.Inner] do not conform to method test's type parameter bounds [T <: h.Self] h.test[Parent.Inner]

even more curiously (or maybe not curiously, since this is a subtype of everything) this works: h.test[Nothing]