RFR(s): 8152617 add missing wildcards to Optional or() andflatMap()
timo.kinnunen at gmail.com
timo.kinnunen at gmail.com
Wed Oct 12 07:33:54 UTC 2016
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.
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