javascalagenericsvariance

Java Type Variance Too Permissive?


In a modern functional language like Scala, type variance is inherent in the type. Here's e.g. Scala's Function1:

trait Function1[-T1, +R] { ... }

contravariant in parameter type and covariant in return type. And here's java's counterpart:

interface Function<T,R> { ... }

Now, to express variance relationship, the "wildcard capture" special syntax is used. For example, the stream's map function is declared as

<R> Stream<T> map(Function<? super T, ? extends R> mapper);

Here, Java shifts the declaration of variance relationship from the type itself to its use as a param in some method signature.

Here's my question. Would I be amiss to say that there cannot be any legitimate usages of Function<T,R> that are not contravariant in T and covariant in R? In other words, does Java's way offer useful extra flexibility not found in Scala, or is it just a lot of repetitive unwieldy boilerplate?


Solution

  • Actually, Scala supports both declaration and use site variance. Specifically, you can specify bounded wildcards just like in Java.

    This already hints that declaration site variance can not replace use site variance in all cases. The reason is that a declaration can only be variant if it is variant in all possible uses. If some uses are variant, but other uses are not, we can't use declaration site variance, but we can use use site variance.

    For instance, class Array[A] can not be declared variant, but the method appendedAll from ArrayOps can employ use site variance

    def appendedAll[B >: A](suffix: Array[_ <: B])(implicit arg0: ClassTag[B]): Array[B]
    

    since it uses covariant methods of suffix.