RFR(s): 8152617 add missing wildcards to Optional or()andflatMap()

timo.kinnunen at gmail.com timo.kinnunen at gmail.com
Wed Oct 12 23:49:05 UTC 2016


I know of in/inout/out parameters from GLSL and, with most all conversions in GLSL being explicit, they are the easiest thing ever to reason about. In GLSL a 2D vector and a float array of 2 can both be accessed using array indexing, but nobody cares if one should be a subtype of another or vice versa. Not me, not the compiler and not the hardware.

Having the caller reason about co-/in-/contra-/use site/declaration site variance or existential types (such as subtypes of Optional that will only exist in the future, if at all) in exchange for an explicit conversion being hidden as an unchecked cast inside a library method (as in this RFR) is a lose-lose situation, in my opinion.

If there is no other use case for existential types in Java then Project Valhalla needs to start making noises about the imminent deprecation of wildcards today.

Have a nice day, 

Sent from Mail for Windows 10

From: Remi Forax
Sent: Wednesday, October 12, 2016 14:44
To: timo kinnunen
Cc: Stuart Marks; core-libs-dev
Subject: Re: RFR(s): 8152617 add missing wildcards to Optional or()andflatMap()

Hi Timo,

----- Mail original -----
> De: "timo kinnunen" <timo.kinnunen at gmail.com>
> À: "Stuart Marks" <stuart.marks at oracle.com>
> Cc: "core-libs-dev" <core-libs-dev at openjdk.java.net>
> Envoyé: Mercredi 12 Octobre 2016 09:33:54
> Objet: RE: RFR(s): 8152617 add missing wildcards to Optional or()	andflatMap()

> Hi,
> I’m going to challenge the consensus a little bit. First, Rémi's example can be
> simplified to this one method which fails to compile with the same error
> message[0]:
> private static Optional<?> simple1(
> Optional<String> o, Function<String, Optional<?>> f
> ) {return o.flatMap(f);}
> Second, although not in the webrev, Optional::map and Optional::flatMap can be
> implemented without needing any unchecked casts:
> public <U, R extends U, O extends Optional<R>> Optional<U> flatMap(
> Function<T, O> mapper
> ) {
> Objects.requireNonNull(mapper);
> return ofNullable(isPresent() ? mapper.apply(get()).orElse(null) : null);
> }
> public <P, Q extends P> Optional<P> map(
> Function<T, Q> mapper
> ) {
> Objects.requireNonNull(mapper);
> return ofNullable(isPresent() ? mapper.apply(get()) : null);
> }
> These are fully, soundly compile-time typed methods with all types exposed and
> completely under the control of the programmer.

You miss the whole point of wildcards, wildcards are use site variance declarations.
When you have a method that takes a Function<T, R> (with T the type of the parameter type and R the return type) as parameter, you can always send a function that takes a super type and returns a subtype.
A function is contravariant on its parameter types and covariant on the return type.
In a sane language, you should be able to declare something like:

  interface Function<T or a super type of T, R or a subtype of R> {
    public R apply(T t);

but because of the backward compatibility of the collections, in Java, you do not use variance annotations when you declare Function but at each time when you use it.
So you have to write:
  <U, V> V foo(Optional<U or a subtype of U> optional, Function<U or a superType of U, V or a subtype of V> function) {

and to make things less readable, instead of using U+ for U or a subtype of U and U- for U and a supertype of U, we use (respectively) ? extends U and ? super U, which can be great when your a beginner because you can read it in english, by example, ? extends U can be read like that: it's a type i don't want to know (?) which is a subtype of U but at the same time because it uses the 'extends' keyword like when you specify the bound of a type variable, you can easily found that using only type variables is more readable, but it's not. A type variable should be used to 'connect' several types in the signature together, like the type of the second parameter is the same as the type of the return type. So fundamentally, a type variable and a wildcard while denote subtyping relationship are two different notations for two different kind of usages.  

> Now, to see if the signature of flatMap is truly the problem, it’s possible to
> write the simple1 method as a map followed by a flatMap with the identity
> function, like this:
> private static Optional<?> simple2(
> Optional<String> o, Function<String, Optional<?>> f
> ) {return o.map(f).flatMap(Function.identity());}
> In this version, the call to flatMap and its argument don’t use any wildcard
> types, both in the version above and in java.util.Optional[1]. Despite that,
> the compiler from jdk1.8.0_102 still gives an error[2] from the flatMap method
> call. This is quite a curious result as here flatMap and identity are reusing
> types that are already used by type inference. If this isn’t sound but using
> wildcards is then I would really like to see that counterexample!
> Some questions that have arisen (and my answers):
> Should APIs account for types that are not denotable in source code? I’d say no,
> such types are bugs in the language.
> Can non-denotable types be eliminated at the library level by adding more
> wildcards? Unlikely, as such types come from using wildcards to begin with.
> Is there a bug in flatMap or is the bug in the language specification? Compared
> to one simple method, type inference rules are much harder to understand and
> thus more likely to contain undiscovered problems.
> What is gained if these wildcards are added? Although simple1 and simple2 would
> compile, you still can’t do anything interesting like calling orElse or
> orElseGet:
> public static void main(String[] args) {
> Optional<String> foo = Optional.ofNullable(args[0]);
> Object o1 = simple1(foo, s -> foo).orElse(o1 = null);
> Object o2 = simple2(foo, s -> foo).orElseGet(() -> null);
> }
> Won’t compile. As far as I can tell, there is no real upside to this change.
> Thanks for your consideration!


> [0] Error:(18, 20) java: method flatMap in class java.util.Optional<T> cannot be
> applied to given types;
>  required: java.util.function.Function<? super
>  java.lang.String,java.util.Optional<U>>
>  found: java.util.function.Function<java.lang.String,java.util.Optional<?>>
>  reason: cannot infer type-variable(s) U
>    (argument mismatch;
>    java.util.function.Function<java.lang.String,java.util.Optional<?>> cannot be
>    converted to java.util.function.Function<? super
>    java.lang.String,java.util.Optional<U>>)
> [1] The <? super T> type can be discounted as its presence doesn’t make a
> difference in this case.
> [2] Error:(21, 27) java: method flatMap in class wildcards.another.Optional<T>
> cannot be applied to given types;
>  required: java.util.function.Function<wildcards.another.Optional<?>,O>
>  found: java.util.function.Function<java.lang.Object,java.lang.Object>
>  reason: inference variable O has incompatible bounds
>    equality constraints: wildcards.another.Optional<?>,T
>    upper bounds: wildcards.another.Optional<capture#11 of ?>,java.lang.Object
> --
> Have a nice day,
> Timo
> Sent from Mail for Windows 10
> From: Stuart Marks
> Sent: Saturday, October 8, 2016 02:28
> To: Stefan Zobel
> Cc: core-libs-dev
> Subject: Re: RFR(s): 8152617 add missing wildcards to Optional or() andflatMap()
> On 10/7/16 3:12 PM, Stefan Zobel wrote:
>>> ... After having looked at lots of generic APIs, it seems to me that a
>>> style has emerged where wildcards are used whenever possible, and type
>>> parameters are used only when necessary, ...
>> Yes, I'm familiar with that kind of reasoning (I think Josh Bloch stated
>> that principle in "Effective Java"). But, to be honest, I never thought
>> that it should apply as a strict rule in all circumstances.
> Yep, it's in Effective Java, near the end of Item 28:
>     “As a rule, if a type parameter appears only once in a method
>     declaration, replace it with a wildcard.”
>> Rhetorical question: do you really think that a signature employing 3
>> wildcards is easier to understand for the proverbial "average Joe" than
>> a bounded type parameter that expresses the sub-type relationship clearly?
>> I do not.
>> But anyway, you probably have to follow the established "style".
>> It's just too bad that most Java programmers I know won't understand the
>> proposed signature of 'flatMap'.
> Turns out that Rémi's example exposes the difference between the wildcard
> approach and the type-parameter approach. Returning to the example,
>     Optional<Integer> oi = Optional.empty();
>     Function<Number, Optional<StringBuilder>> fm = n -> Optional.empty();
>     Optional<CharSequence> ocs = oi.flatMap(fm);
> If the flatMapper function itself has a wildcard type, for example,
>     Function<Number, Optional<? extends CharSequence>> fm = n -> Optional.empty();
> then this will still work with the wildcard approach but fail with the
> type-parameter approach. As Rémi also pointed out, a wildcarded type can result
> from the capture of a type with a wildcarded type parameter.
> Based on this, I believe the nested wildcard approach to be the correct one.
> s'marks

More information about the core-libs-dev mailing list