2

I'm working on a functional programming library for Java, and I've hit a frustrating issue. I've got the following functions in my Option<V> class:

/**
 * Returns an Option containing the provided value.
 */
public static <V> Option<V> some(V value)
{...}

/**
 * Returns an Option that contains nothing.
 */
public static <V> Option<V> none()
{...}

/**
 * Decide control flow based on whether this is Some or None,
 * returning the result of the chosen operation.
 *
 * @param some the operation to perform on the contained value if there is one
 * @param none the operation to perform if there is no contained value
 * @return the result of the matched operation
 */
public <T> T matchThen(Function<? super V, ? extends T> some, Supplier<? extends T> none)
{...}

I'm currently implementing a method, orElse, that uses the methods above, and is implemented like so:

/**
 * Returns this if it is a Some, otherwise computes an Option from the given Supplier.
 * @param supp the supplier to compute the other Option
 * @return the first present value or None
 */
public Option<V> orElse(Supplier<? extends Option<? extends V>> supp)
{
    return matchThen(
            Option::some, // if there is a value, wrap it back up and return it
            supp::get     // if there isn't a value, get one from the supplier
    );
}

IDEA reports an error with the use of Option::some in orElse:

Bad return type in method reference: cannot convert Option<V> to ? extends Option<? extends V>

My understanding of wildcard captures was that Option<? extends V> accepts any object of type Option<T> where <T extends V>, which would mean that an Option<V> is definitely an Option<? extends V>. Likewise, I'd expect that a ? extends Option<? extends V> type parameter would accept the same thing (an Option<T> as above or an Option<V>).

So, I have two questions:

  1. Why is this error occurring? I've come to expect this to be the case where ? extends Type<? extends V> works. Is my expectation being misapplied in this case? I can see how I could be misreading the error, as there's a few types involved, and I'll admit I'm not certain my above analysis is correct.
  2. Is there a fix to the method signatures that doesn't sacrifice flexibility, or does so minimally? I'd love for users of this library to not have to worry about actually-compatible values and functions raising type errors because of my API.

Note

I'm not asking for an implementation of orElse that avoids this type error; I have a tool for that already, and I don't care too much if my implementation uses minor workarounds. My main concern is that users of this library may face this type of issue when implementing their own solutions, so I want to make these methods as flexible type-wise as I can, which is why I have all these wildcards in the first place.

Zoey Hewll
  • 4,788
  • 2
  • 20
  • 33
  • To the point as why the error is occurring ```Function super V, ? extends T>``` means provided a value `V` you tend to return a something that extends `T`, that is what your `some` method doesn't do. It returns back the same type wrapped as an Option, i.e. `Option`. – Naman Apr 16 '20 at 13:25
  • Here `T` is `Option` – Zoey Hewll Apr 16 '20 at 13:28

1 Answers1

2

Aah, nested wildcards.

Don't behave as you expect. Quick search gives Multiple wildcards on a generic methods makes Java compiler (and me!) very confused as a candidate for canonical answer.

Effectively you are trying to get the type returned by supp.get() (Option<? extends V>) to be returned by orElse (Option<V>), which is not happening.

Presumably the interface for Option only uses V in a producer role, but there's no way of expressing that in Java.

An easy fix would be to make the return type Option<? extends V>.

You can retain the nicer interface by replacing the second argument of matchThen with a lambda function that unwraps the Supplier and the Option<? extends V> to a straight V reference, then rewraps as an Option<V>.

Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • Regarding your last paragraph there on un/rewrapping the Option, I've got a method (the workaround I mentioned), `static Option cast(Option extends V>)` for that exact purpose (handling covariance), but I'd rather not need to expose it as part of the API – Zoey Hewll Apr 17 '20 at 04:34