I don’t have an answer, but my guess would be some issue with Java compatibility.
As you may know, in Java, you can only specify variance where a type is used, not where it’s declared. If you look at
java.lang.Comparator, you’ll see that it’s defined as
interface Comparator<T> — as there’s no other way to do it. Instead, a few of its methods have wildcards, (e.g.
thenComparing(Comparator<? super T> other)).
Kotlin, on the other hand, allows declaration-site variance too, in the way that you suggest. That builds knowledge about the variance into the class declaration, and avoids a lot of extra typing (and thinking) wherever it’s used.
Kotlin/JVM makes very heavy use of the existing Java standard library. In a few cases, it uses its own wrappers with tweaks to make things easier to use in Kotlin — for example, adding
String.indices() so you can loop over the characters in a String. That even extends to adding variance where appropriate; for example
kotlin.collections.List is defined as
interface List<out E> : Collection<E> (unlike the sub-interface
MutableList, which is invariant.)
But, as you point out, they didn’t do so for
The direct reason is that that’s not a wrapper class, but a simple typealias for
java.util.Comparator. So it doesn’t have the option of changing variance, or anything else.
However, I don’t know why they didn’t make it a wrapper class. Using variance in the way you suggest is an obvious improvement — in fact, section 9.3.4 of the book Kotlin In Action (which was written by two members of the Kotlin development team, and is excellent!) gives
interface Comparator<in T> as an example when explaining contravariance!
Maybe they felt that ultimately a wrapper wasn’t justified; or maybe it revealed some variance problem in the Java class that couldn’t be fixed that way; or maybe it would cause some other problem interoperating with Java code that defines or uses comparators.
Note that you can still specify the variance when using a Comparator, e.g.:
val c: Comparator<in String> = //…
Thank you for your long reply.
java.util.Comparator interface has wildcards, but all wildcards are
? super T which perfectly fits contravariance.
Comparator is one of the most prominent interfaces, so if it can’t be solved with a clean typealias I would have expected some intrinsic solution of which you have named a few.
Maybe it’s worth more investigation and a youtrack issue?
Testing around a little, the problem is actually the
thenComparing methods. The fact that
T is used as a parameter is breaking the contravariance. Kotlin has extension methods for this (called
then), so they are not actually needed and are just breaking things.
I guess it is too complicated to hide them in Kotlin and cover all the corner cases, like a comparator implementation that choses to override the default
thenComparing method (unlikely but possible).
Java 8 introduced these methods, so I wouldn’t be suprised if Kotlin originally provided contravariance on
Comparator and had to give it up for Java 8 interoperability.