Should Stream<T> be covariant?

Brian Goetz brian.goetz at oracle.com
Wed Jan 30 12:55:47 PST 2013


Of course it is possible to remove the invariant BinaryOperator -- it 
could be replaced with

   BiFunction<? super T, ? super T, ? extends T>

Clearly that would be less convenient in some cases, and would have some 
cost in API readability, since everyone knows what a BinaryOperator is 
but one has to read more carefully to figure out what the BiFunction is 
supposed to do.

So I'll turn your question around to you: what benefits would we get 
from doing so, that would make it worth suffering the consequences of 
the above?

On 1/30/2013 3:32 PM, Ali Lahijani wrote:
> As a mental experiment, I tried to create a "constructive proof"
> that Stream<T> is covariant in the variable T.
> My basic idea is that if it is covariant, it should be possible to elevate
> an expression of type Stream<? extends T> to an expression of type
> Stream<T> in a suitable "mechanical" fashion. So I tried to implemented a
> function
>
>      public static <T> Stream<T> elevate(Stream<? extends T> delegate)...
>
> To implement the above method, one of course first needs to prove
> that Spliterator<T>, Iterator<T> and Optional<T> are covariant in T. But
> they are quite straightforward.
>
>      public static <T> Optional<T> elevate(Optional<? extends T> delegate)...
>      public static <T> Iterator<T> elevate(Iterator<? extends T> delegate)...
>      public static <T> Spliterator<T> elevate(Spliterator<? extends T>
> delegate)...
>
> The result of the experiment is pasted below. Almost all methods pass the
> test, which, I suppose, means that the Stream API has an exceptionally
> clean design.
> (It is worth mentioning that not all previous versions of Stream passed the
> same test so smoothly.)
>
> But as the compiler will tell you, there is a problem with two methods,
> both cases because they use BinaryOperator<T>:
>
>              public T reduce(T identity, BinaryOperator<T> reducer);
>              public Optional<T> reduce(BinaryOperator<T> reducer);
>
> BinaryOperator<X> is invariant in X, so passing T to BinaryOperator removes
> covariance of Stream. The first method is not needed, it is just a sugar
> around the third from of reduce which takes three parameters:
>
>              public T reduce(T identity, BinaryOperator<T> reducer) { return
> reduce(identity, reducer, reducer); }
>
> But to be able to emulate the second form, reduce(BinaryOperator<T>), there
> should be a method to decompose a Stream as a head/tail pair. Given that
> facility, one can emulate reduce(reducer) as tail.reduce(head, reducer,
> reducer).
>
> So here is my question: is it possible that the non-covariant reduce
> methods be removed, and a method for head/tail decomposition is added
> instead?
>
> Best
> Ali
>


More information about the lambda-dev mailing list