Description
Not sure if it is a bug or an unintuitive/undocumented behavior.
trait A
trait B
trait Covariant[F[+_]] {
trait G[+X]
def fx: F[A & B] = ??? // fy: Type Mismatch Error
def fy: F[A] & F[B] = fx
def gx: G[A & B] = gy
def gy: G[A] & G[B] = gx
}
Above, fx = fy
gives a type mismatch error. However, gx = gy
does not. My guess is, in case of fx = fy
, the only fact the compiler knows about F
is that it is covariant. According to the covariance laws, fx = fy
is illegal. However, in case of gx = gy
, the compiler knows the actual type G
. It hence can compute what exactly members should G[A & B]
and G[A] & G[B]
have, and see that these members are identical, and hence allow for gx = gy
.
However, this logic does not stand the contravariance test:
trait Contravariant[F[-_]] {
trait G[-X]
def fx: F[A & B] = fy
def fy: F[A] | F[B] = ??? // fx: Type Mismatch Error
def gx: G[A & B] = gy
def gy: G[A] | G[B] = ??? // gx: Type Mismatch Error
}
Here, fy = fx
is illegal according to contravariance laws, and the fact that F
is contravariant is the only thing the compiler knows about F
. However, if the Covariant
example reasoning above is correct, then the compiler will take into account the information available about the concrete G
trait. Following the covariant example logic, in case of gy = gx
, the compiler should be able to compute that G[A] | G[B]
type has the same members as G[A & B]
. And, if gx = gy
is allowed for the covariant case, following the same logic, gy = gx
should be allowed for the contravariant case. However, this does not happen, and the compiler appears to be guided only by the contravariance laws, ignoring this time the distinction between the type parameters and traits.
A similar case makes its appearance in the documentation on intersection types, precisely, the snippet of:
val x: A & B = new C
val ys: List[A & B] = x.children
There, the documentation says:
The type of
children
inA & B
is the intersection ofchildren
's type inA
and its type inB
, which isList[A] & List[B]
. This can be further simplified toList[A & B]
because List is covariant.
The simplification can indeed be done when defining C
, so that the children
method returns List[A & B]
, because List[A & B] <: List[A] & List[B]
. However, what exactly happens in case of val ys: List[A & B] = x.children
?
val x: A & B = new C
def f(x: C) = ???
f(x)
The above fails with
[error] 14 | f(x)
[error] | ^
[error] | Found: (A & B)(Official.x)
[error] | Required: C
So, there is no chance that when doing val ys: List[A & B] = x.children
, the compiler knows that x
is of type C
. The compiler thinks that x
is of type A & B
. And what should the type of children
of A & B
be? According to the doc on the intersection types:
If a member appears in both
A
andB
, its type inA & B
is the intersection of its type inA
and its type inB
. For instance, assume the definitions:
Hence, the type of children
in A & B
must be List[A] & List[B]
, and List[A] & List[B] >: List[A & B]
. I can't see any reason for the compiler to make the type in question a more special List[A & B]
.
Hence, the assignment val ys: List[A & B] = x.children
should not be possible, because x
is of type A & B
and x.children
is inferred to List[A] & List[B]
, and List[A & B] <: List[A] & List[B]
.
One way it can be possible is if the compiler utilizes the information it has about the List
type and computes that List[A] & List[B] =:= List[A & B]
. However, this does not appear to be true:
[error] 15 | implicitly[List[A & B] =:= List[A] & List[B]]
[error] | ^
[error] |no implicit argument of type List[A & B] =:= List[A] & List[B] was found for parameter ev of method implicitly in object DottyPredef
And, if this was the case, then why does it not work with the union type as described above?
So, is it a bug, or is there some logic regarding subtyping rules with covariance/contravariance and unions/intersections that the documentation does not mention?
I tested the above code on dotty 0.13.0-RC1 and 0.14.0-bin-20190222-789269d-NIGHTLY.