1

I had a (looong) version of this problem posted yesterday here: Trouble with type variance

To cut the (really) long story short, this:

 class A[-P, T <: P]

does not compile (it complains, that "P occurs in covariant position in type <: P of type T"). An answer to my earlier question suggested that the declaration is indeed invalid, because A[Foo, Bar] would be a subclass of A[Nothing, Bar], but the latter is illegal (Bar isn't a subclass of Nothing).

I don't think that's reason enough. By that logic, something like this should be illegal too: class B[+T <: String] - B[String] should be a subclass of B[Any] but the latter is invalid.

Moreover, this:

class C[T, -P >: T] 

Actually does compile. Isn't this essentially the same exact thing as A above?

Dima
  • 39,570
  • 6
  • 44
  • 70

1 Answers1

1

These are subtly different.

class Test[-P, T <: P]

says "let P be a type parameter that varies contravariantly and unboundedly, and let T be a subtype of P." That is, T is completely ignored when checking whether a type is valid for P. Only then is T constrained to be a subtype of P. This is illegal, for the reason you already know. You can start with Test[Any, String], and because P is unboundedly contravariant, it can become Test[Nothing, String], and suddenly you have String <: Nothing.

class tseT[T, -P :> T] // Code highlighters HATE him. Click to find out why.

says "let T be a type parameter that doesn't vary and is unbounded, and then let P be a contravariant type parameter that is lower-bounded by T." This is tiny bit different, because this time P's variance is constrained by the lower bound T. P still varies contravariantly, but it cannot vary lower than T, or else it will violate P >: T. This is safe, because you can start with tseT[String, Any], and then P can only vary down as long as it is still a supertype of String, so it is illegal for P to vary down to Nothing.

Note that type parameters are processed left-to-right. This is why the first example is declared invalid, instead of inferring the bound P >: T, because T is not declared yet when P is processed, and such a bound would be a forward-reference anyway.

HTNW
  • 27,182
  • 1
  • 32
  • 60
  • I still don't get why first example is illegal, and the second one is not: sure, `Test[Nothing, String]` is invalid, but so is `tseT[String, Nothing]` ... – Dima Aug 19 '17 at 12:31
  • Correct. The thing is that with the first one, `P` can vary as much as likes, without regard for with `T` is. Therefore, `P` can vary into `Nothing`, no matter what `T` is, and break things. In the second, `P` can only vary *down to* `T`. You don't just constrain `P >: T`, you constrain the *variance* of `P` too, so it can't vary below `T`. The first one is therefore invalid, for it can always break, but the second one is valid, and the compiler just won't let you write the type `tseT[String, Nothing]`, while other instantiations work. – HTNW Aug 19 '17 at 16:00