Use of super in type parameters
CompletableFuture currently has a method like this: public CompletableFuture<Void> acceptEither (CompletableFuture<? extends T> other, Consumer<? super T> block) { return doAcceptEither(other, block, null); } But that signature is not quite correct (not as general as it could be). The "correct" signature is public <U super T> CompletableFuture<Void> acceptEither (CompletableFuture<? extends U> other, Consumer<U> block) { return doAcceptEither(other, block, null); } but that fails to compile, because type parameters can only be constrained by extends, not super. Is implementing this on the radar? Angelika claims "lower bounds for type parameters make no sense" http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#Wh... ? but I am finding that hard to believe. Is she right? For comparison, the equivalent static method can be made to do what we want: public static <U> CompletableFuture<Void> acceptEither (CompletableFuture<? extends U> f, CompletableFuture<? extends U> other, Consumer<U> block) { return ... }
I have encountered on stackoverflow.com several legit use cases of lower bound. And the other day Ali Lahijani raised the question that Stream.reduce(BinaryOperator) breaks convariance of Stream, and I thought that the root problem is lack of lower bound - the method would have had a better signature Stream<T> <U super T> Optional<U> reduce(BinaryOperator<U> accumulator) So I don't think there's a lack of use cases for lower bound. (But I have no idea how difficult it is to support it) Zhong Yu On Mon, Apr 15, 2013 at 2:37 PM, Martin Buchholz <martinrb@google.com> wrote:
CompletableFuture currently has a method like this:
public CompletableFuture<Void> acceptEither (CompletableFuture<? extends T> other, Consumer<? super T> block) { return doAcceptEither(other, block, null); }
But that signature is not quite correct (not as general as it could be). The "correct" signature is
public <U super T> CompletableFuture<Void> acceptEither (CompletableFuture<? extends U> other, Consumer<U> block) { return doAcceptEither(other, block, null); }
but that fails to compile, because type parameters can only be constrained by extends, not super. Is implementing this on the radar? Angelika claims "lower bounds for type parameters make no sense" http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#Wh... ?
but I am finding that hard to believe. Is she right? For comparison, the equivalent static method can be made to do what we want:
public static <U> CompletableFuture<Void> acceptEither (CompletableFuture<? extends U> f, CompletableFuture<? extends U> other, Consumer<U> block) { return ... }
With the coming of lambda, it is more likely that people will be creating APIs with "not quite correct" generic types, as we are doing in CompletableFuture. Which is pretty bad for a feature that is designed specifically to provide compile time safety. It would be nice to at least have an acknowledgment that there is a problem here, even if it will not be fixed for this release. On Mon, Apr 15, 2013 at 12:50 PM, Zhong Yu <zhong.j.yu@gmail.com> wrote:
I have encountered on stackoverflow.com several legit use cases of lower bound. And the other day Ali Lahijani raised the question that Stream.reduce(BinaryOperator) breaks convariance of Stream, and I thought that the root problem is lack of lower bound - the method would have had a better signature Stream<T> <U super T> Optional<U> reduce(BinaryOperator<U> accumulator) So I don't think there's a lack of use cases for lower bound. (But I have no idea how difficult it is to support it)
Zhong Yu
On Mon, Apr 15, 2013 at 2:37 PM, Martin Buchholz <martinrb@google.com> wrote:
CompletableFuture currently has a method like this:
public CompletableFuture<Void> acceptEither (CompletableFuture<? extends T> other, Consumer<? super T> block) { return doAcceptEither(other, block, null); }
But that signature is not quite correct (not as general as it could be). The "correct" signature is
public <U super T> CompletableFuture<Void> acceptEither (CompletableFuture<? extends U> other, Consumer<U> block) { return doAcceptEither(other, block, null); }
but that fails to compile, because type parameters can only be constrained by extends, not super. Is implementing this on the radar? Angelika claims "lower bounds for type parameters make no sense"
http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#Wh...
?
but I am finding that hard to believe. Is she right? For comparison, the equivalent static method can be made to do what we want:
public static <U> CompletableFuture<Void> acceptEither (CompletableFuture<? extends U> f, CompletableFuture<? extends U> other, Consumer<U> block) { return ... }
On Wed, Apr 17, 2013 at 4:53 PM, Martin Buchholz <martinrb@google.com> wrote:
With the coming of lambda, it is more likely that people will be creating APIs with "not quite correct" generic types, as we are doing in
I believe that we can tweak generic signatures in APIs without breaking calling codes. No API user really pays attention to all these questions marks in a signature, he'll just go with instinct about co/contravariances, deduced from the nature of the method, so his code shouldn't break when the signature is changed for the better, because the args he provides are all in the proper types. For example, if a method signature is supposed to be <S super T> foo(List<S>, List<S>) but we declare it today as, regrettably, foo(List<? super T>, List<? super T>) we don't really expect users to supply List<S1> and List<S2> where S1!=S2. If a user does that, it conflicts with the implicit/explicit contract of the method, the type system couldn't help to catch the mistake today, but it's a bug regardless. Zhong Yu
CompletableFuture. Which is pretty bad for a feature that is designed specifically to provide compile time safety. It would be nice to at least have an acknowledgment that there is a problem here, even if it will not be fixed for this release.
On Mon, Apr 15, 2013 at 12:50 PM, Zhong Yu <zhong.j.yu@gmail.com> wrote:
I have encountered on stackoverflow.com several legit use cases of lower bound. And the other day Ali Lahijani raised the question that Stream.reduce(BinaryOperator) breaks convariance of Stream, and I thought that the root problem is lack of lower bound - the method would have had a better signature Stream<T> <U super T> Optional<U> reduce(BinaryOperator<U> accumulator) So I don't think there's a lack of use cases for lower bound. (But I have no idea how difficult it is to support it)
Zhong Yu
On Mon, Apr 15, 2013 at 2:37 PM, Martin Buchholz <martinrb@google.com> wrote:
CompletableFuture currently has a method like this:
public CompletableFuture<Void> acceptEither (CompletableFuture<? extends T> other, Consumer<? super T> block) { return doAcceptEither(other, block, null); }
But that signature is not quite correct (not as general as it could be). The "correct" signature is
public <U super T> CompletableFuture<Void> acceptEither (CompletableFuture<? extends U> other, Consumer<U> block) { return doAcceptEither(other, block, null); }
but that fails to compile, because type parameters can only be constrained by extends, not super. Is implementing this on the radar? Angelika claims "lower bounds for type parameters make no sense"
http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#Wh... ?
but I am finding that hard to believe. Is she right? For comparison, the equivalent static method can be made to do what we want:
public static <U> CompletableFuture<Void> acceptEither (CompletableFuture<? extends U> f, CompletableFuture<? extends U> other, Consumer<U> block) { return ... }
Thanks, all, for the history lesson.
On Apr 17, 2013, at 6:11 PM, Zhong Yu <zhong.j.yu@gmail.com> wrote:
On Wed, Apr 17, 2013 at 4:53 PM, Martin Buchholz <martinrb@google.com> wrote:
With the coming of lambda, it is more likely that people will be creating APIs with "not quite correct" generic types, as we are doing in
I believe that we can tweak generic signatures in APIs without breaking calling codes.
It's one thing to make a change that won't break callers. It's another to make a change that won't break implementers.
<S super T> foo(List<S>, List<S>) but we declare it today as, regrettably, foo(List<? super T>, List<? super T>)
Unfortunately (without some sort of language change...), neither one of these signatures can be used to override the other. So if somebody extends my class and overrides my signature, when I change it to the other one later, their code will break. —Dan
On Apr 15, 2013, at 1:37 PM, Martin Buchholz <martinrb@google.com> wrote:
CompletableFuture currently has a method like this:
public CompletableFuture<Void> acceptEither (CompletableFuture<? extends T> other, Consumer<? super T> block) { return doAcceptEither(other, block, null); }
But that signature is not quite correct (not as general as it could be). The "correct" signature is
public <U super T> CompletableFuture<Void> acceptEither (CompletableFuture<? extends U> other, Consumer<U> block) { return doAcceptEither(other, block, null); }
but that fails to compile, because type parameters can only be constrained by extends, not super. Is implementing this on the radar?
My favorite example: interface Optional<T> { <S super T> S get(S alternate); } Optional<Integer> o = ...; Number n = o.get(0.0); We were _this_ close to adding this, or something like it, with Lambda. In the end, it didn't make the cut. The good news is that capture variables already have lower bounds, and inference in Java 8 has been enhanced quite a bit, such that it can mostly handle things like this. So it seems like it would be a relatively modest task to support the feature someday.
Angelika claims "lower bounds for type parameters make no sense" http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#Wh...
but I am finding that hard to believe. Is she right?
Lots of people said a lot of wrong things about generics in the heady Java 5 days. One or two of those things even ended up in the language spec. :-( But no, "I can't think of why this would be useful" does not imply "this makes no sense." (To be fair, I think we all make that leap from time to time.)
For comparison, the equivalent static method can be made to do what we want:
public static <U> CompletableFuture<Void> acceptEither (CompletableFuture<? extends U> f, CompletableFuture<? extends U> other, Consumer<U> block) { return ... }
Yep, that's the workaround for now. Or use a weaker signature for your instance method. (Or both.) On Apr 17, 2013, at 3:53 PM, Martin Buchholz <martinrb@google.com> wrote:
With the coming of lambda, it is more likely that people will be creating APIs with "not quite correct" generic types, as we are doing in CompletableFuture. Which is pretty bad for a feature that is designed specifically to provide compile time safety.
True. I've made that argument for other axed features as well -- we punt now, and solving the problem later will just be harder, due to further compatibility constraints. Ultimately, it would be great to make everything perfect out of the gate; but that's not practical. (And in fact, "out of the gate" in this case was 9 years ago.) Besides, we have to have something fun to do next time. :-) —Dan
participants (3)
-
Dan Smith
-
Martin Buchholz
-
Zhong Yu