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

Remi Forax forax at univ-mlv.fr
Fri Oct 7 22:35:26 UTC 2016


----- Mail original -----
> De: "Stuart Marks" <stuart.marks at oracle.com>
> À: "Paul Sandoz" <paul.sandoz at oracle.com>
> Cc: "core-libs-dev" <core-libs-dev at openjdk.java.net>
> Envoyé: Vendredi 7 Octobre 2016 21:22:09
> Objet: Re: RFR(s): 8152617 add missing wildcards to Optional or() and	flatMap()

> On 10/7/16 11:23 AM, Paul Sandoz wrote:
>>>    flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)
>>
>> Optional is final so why do you need to express “? extends Optional” ?
> 
> The short answer is, it doesn't work if you don't have it. :-)
> 
> The theoretical answer is that in this context, "? extends P<Q>" means "some
> subtype of P<Q>" and not necessarily just a subclass of P.
> 
> (And even though Optional is final, it's not "permanently final" in that a
> hypothetical future version of the class library might change it to non-final,
> allowing subclasses.)
> 
> This issue is covered in Angelika Langer's Generics FAQ entry, "What do
> multi-level (i.e., nested) wildcards mean?" [1] Note that the answer begins, "It
> depends." :-) I also note in passing that I've read this about five times and
> I'm still not quite sure I understand it entirely.
> 
> For me, the best explanation comes from looking at examples. First, the history
> is that the signature in Java 8 is:
> 
>   #1    flatMap(Function<? super T, Optional<U>> mapper)
> 
> I believe Rémi originally proposed something like this (although it was about
> or(), the same issue applies to flatMap()):
> 
>   #2    flatMap(Function<? super T, ? extends Optional<U>> mapper)
> 
> The suggested fix that ended up in bug report was this:
> 
>   #3    flatMap(Function<? super T, Optional<? extends U>> mapper)
> 
> But this doesn't work for reasons I explain below. Finally, I'm proposing:
> 
>   #4    flatMap(Function<? super T, ? extends Optional<? extends U>> mapper)
> 
> In researching old email threads and chasing links, I ran across an example from
> Stefan Zobel [2] that fails with #3. He had an alternative that didn't seem
> quite right to me, so I ended up with #4.
> 
> I've adapted Stefan's example as follows:
> 
>	Optional<Integer> oi = Optional.empty();
>	Function<Number, Optional<StringBuilder>> fm = n -> Optional.empty();
>	Optional<CharSequence> ocs = oi.flatMap(fm);
> 
> The flatmapper function 'fm' returns Optional<StringBuilder>. In the assignment
> on the last line, U is CharSequence, so we can compare this to the various
> signatures shown above with U filled in.
> 
> Case #1 fails because Optional<StringBuilder> is incompatible with
> Optional<CharSequence>. This is the usual "generics are invariant thing". Even
> though SB is a subtype of CS, Optional<SB> isn't a subtype of Optional<CS>.
> 
> Case #2 fails because adding a wildcard there doesn't help matters, since
> Optional<SB> is still unrelated to Optional<CS>.
> 
> Now for the tricky part. :-)
> 
> Surely case #3 should work, because adding an inner wildcard provides a
> subtyping relationship, so Optional<SB> is a subtype of Optional<? extends CS>.
> 
> That much is true, but these are nested within the Function<> generic type, so
> the "generics are invariant" rule applies again. Thus,
> 
>     Function<..., Optional<StringBuilder>>
> 
> is not a subtype of
> 
>     Function<..., Optional<? extends CharSequence>>
> 
> To get around this, we have to add the outer wildcard as well, so that
> 
>     Function<..., Optional<StringBuilder>>
> 
> is a subtype of
> 
>     Function<..., ? extends Optional<? extends CharSequence>>
> 
> So that's what ended up in the signature.
> 
> Similar analysis applies to the or() case.
> 
> Now awaiting a message from Rémi telling me my explanation is incorrect in 3...
> 2... 1... :-) :-)

no i think you are right :)

here is an example that shows that the signature of Optional.flatmap is not correct
  public static <T> Optional<T> first(List<T> list) {
    return list.stream().findFirst();
  }
  
  public static void bar(List<? extends CharSequence> list) {
    Function<String, Optional<? extends CharSequence>> fun = s -> first(list);
    Optional.of("foo").flatMap(fun );
  }
  
  public static void main(String[] args) {
    bar(List.of("hello"));
    bar(List.of(new StringBuilder("hello")));
  }

while Optional is final, you can still create an Optional<? extends CharSequence> when doing a capture, i.e. when you see a wildcard type as a parameterized type.
here, first() is called with a List<? extends CharSequence> so T = ? extends CharSequence so the return type of first() is an Optional<? extends CharSequence>.

> 
> s'marks

Rémi

> 
> 
> 
> [1]
> http://angelikalanger.com/GenericsFAQ/FAQSections/TypeArguments.html#What%20do%20multilevel%20wildcards%20mean?
> 
> [2] https://sourceforge.net/p/streamsupport/tickets/125/#2d90


More information about the core-libs-dev mailing list