Parametrized method and cyclic inference

Sam Pullara sam at sampullara.com
Fri Nov 2 21:11:27 PDT 2012


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