Consider the following code
trait Foo[T] {
def one: Foo[_ >: T]
def two: T
def three(x: T)
}
def test[T](f: Foo[T]) = {
val b = f.one
b.three(b
The problem is the compiler thinks that
b.two: _>:T
b.three(_>:T)
i.e. two is a supertype of T and three requires a supertype of T. But a supertype of T is not necessarily assignment compatible with another supertype of T, as in this example:
A >: B >: C
def get:A
def put(B)
put(get) // type mismatch
So if all the information we have is that they are supertypes of T then we cannot do this safely. We have to explicitly tell the compiler that they are the same supertype of T.
trait Foo[T] {
type U <: T
def one: Foo[U]
def two: T
def three(x: T)
}
Then just set U
when you implement the trait:
val x = new Foo[Dog]{
type U = Mammal
...
I would prefer this approach over the existential types due to the cleaner syntax and the fact that this is core Scala and does not need the feature to be imported.
def one: Foo[_ >: T]
is equivalent to
def one: Foo[U >: T] forSome {type U >: T}
this one instead works
def one: Foo[U forSome {type U >: T}]
I do not however understand why this would make a difference. It seems like it should not to me. (shrug)