Parametrized method and cyclic inference

Brian Goetz brian.goetz at oracle.com
Sat Nov 3 05:01:47 PDT 2012


Yeah, conditionals are awful.  We're working on them too.  But lots of 
historical landmines to avoid :(

On 11/3/2012 12:11 AM, Sam Pullara wrote:
> I've also had issues with ?:, <> in combination with lambda return types. It looks like this I because I expect the type declaration on the left to flow through.
>
> Sam
>
> Example declaration:
>
>      public static <T, V, W> Match<T, V> match(Extractor<T, W> e, Mapper<W, V> c) {
>
> Works:
>          Match<String, Integer> matcher = match((String s) -> s.equals("1") ? new Optional<>(1) : null, s -> 1);
>
> Doesn't work:
>          Match<String, Integer> matcher2 = match(s -> s.equals("1") ? new Optional<>(1) : null, s -> 1);
>
> error: method match in class Match<T#2,V#2> cannot be applied to given types;
> required: Extractor<T#1,W>,Mapper<W,V#1>
> found: (s)->s.equ[...] null,(s)->1
> reason: cyclic inference - cannot infer target type for given lambda/method reference expression
> where T#1,V#1,W,T#2,V#2 are type-variables:
> T#1 extends Object declared in method <T#1,V#1,W>match(Extractor<T#1,W>,Mapper<W,V#1>)
> V#1 extends Object declared in method <T#1,V#1,W>match(Extractor<T#1,W>,Mapper<W,V#1>)
> W extends Object declared in method <T#1,V#1,W>match(Extractor<T#1,W>,Mapper<W,V#1>)
> T#2 extends Object declared in class Match
> V#2 extends Object declared in class Match
>
> Declaration:
>      public <W> Match<T, V> or(Extractor<T, W> e, Mapper<W, V> c) {
>
> Works:
>          Match<String, Integer> matcher3 = matcher.or(s -> s.equals("2") ? new Optional<>(2) : Optional.<Integer>empty(), i -> 2);
>
> Doesn't work:
>          Match<String, Integer> matcher3 = matcher.or(s -> s.equals("2") ? new Optional<>(2) : Optional.empty(), i -> 2);
>
> error: no suitable method found for or((s)->s.equ[...]pty(),(i)->2)
> method Match.<T#1,V#1,W#1>or(Match<T#1,V#1>,Extractor<T#1,W#1>,Mapper<W#1,V#1>) is not applicable
> (cannot infer type-variable(s) T#1,V#1,W#1
> (actual and formal argument lists differ in length))
> method Match.<W#2>or(Extractor<String,W#2>,Mapper<W#2,Integer>) is not applicable
> (cannot infer type-variable(s) W#2
> (argument mismatch; bad return type in lambda expression
> bad type in conditional expression; Optional<Integer> cannot be converted to Optional<Object>))
> where T#1,V#1,W#1,W#2,T#2,V#2 are type-variables:
> T#1 extends Object declared in method <T#1,V#1,W#1>or(Match<T#1,V#1>,Extractor<T#1,W#1>,Mapper<W#1,V#1>)
> V#1 extends Object declared in method <T#1,V#1,W#1>or(Match<T#1,V#1>,Extractor<T#1,W#1>,Mapper<W#1,V#1>)
> W#1 extends Object declared in method <T#1,V#1,W#1>or(Match<T#1,V#1>,Extractor<T#1,W#1>,Mapper<W#1,V#1>)
> W#2 extends Object declared in method <W#2>or(Extractor<T#2,W#2>,Mapper<W#2,V#2>)
> T#2 extends Object declared in class Match
> V#2 extends Object declared in class Match
>
> On Nov 2, 2012, at 5:35 PM, Remi Forax <forax at univ-mlv.fr> wrote:
>
>> On 11/02/2012 11:19 PM, Dan Smith wrote:
>>> On Nov 2, 2012, at 10:48 AM, Remi Forax <forax at univ-mlv.fr> wrote:
>>>
>>>> Hi guys,
>>>> I've tried to take a big corpus of code and to refactor all inner-classes to use lambda instead.
>>>>
>>>> The good news is that on 23 uses of inner classes, 20 can be retrofited to use lambdas
>>>> because the target type is a SAM and they don't require a strong identity (this is not used).
>>>> The bad news is that among the 20 that can be retrofited, 17 can not be retrofited using
>>>> the syntax that doesn't specified the type of the formal parameter i.e. the natural syntax
>>>> because the compiler complains that there is a cyclic inference.
>>>>
>>>> The 17 snippets of code can be covered by 3 cases:
>>>>   static <T> void m(T t1, T t2) {
>>>>     // empty
>>>>   }
>>>>
>>>>   public static void main(String[] args) {
>>>>     m(x -> 3, x -> 4);   // case 1
>>>>     Set<Mapper<Integer, Object>> set = Collections.singleton(x -> 3);   // case 2
>>>>     List<Mapper<Integer, Object>> list = Arrays.asList(x -> 1);  // case 3
>>>>   }
>>>>
>>>> To sumarrize, it's currently impossible to call a parametrized method with an untyped lambda,
>>>> the inference will just choke.
>>>>
>>>> I think instead that if there is a cyclic inference,
>>>> the return type should be used to try to infer the formal parameter type of the lambda,
>>>> at least, it will solve case 2 and 3.
>>> Thanks!  Feedback from real code is very useful.
>>
>>
>>> #2 and #3 are interesting.  In general, we've nailed down a strategy for inference that depends on a target type.  But this is special.
>>>
>>> The target type is an inference variable, T.  Note that this is _not_ a functional interface.  Everything we've done to this point has bailed out early if the target of a lambda is not a functional interface.
>>>
>>> An alternative would be to initially hope that T will be a functional interface, wait for T to be resolved, and then go from there.  There are aspects of that that feel a little more complex than I would like, but it's probably doable.
>>
>> I think that if we have cyclic inference, it's better to back propagate the result type and to try to use it as a function interface.
>>
>> By example, for
>>
>> Set<Mapper<Integer, Object>> set = Collections.singleton(x -> 3);
>>
>> Clearly, it's a cyclic inference, because singleton is defined like this <T> Set<T> signleton(T element),
>> instead of trying to know if T is a function interface, in my opinion, it's better to try to infers T from the result type,
>> here, Set<Mapper<Integer, Object>>, because the return type is Set<T>, then T is Mapper<Integer,Object>,
>> and now you can restart your inference of the lambda expression with the type of T.
>>
>> Note that this algorithm also works with diamond syntax which is the other expression with lambda expression
>> that need the target type, i.e. works backward in the type checker.
>>
>> Rémi
>>
>


More information about the lambda-spec-experts mailing list