Hi Jochen,

but in what sense is any of these examples confusing for the user ? Type inference is not magic, and it can quickyl become a hard mathematical problem (https://en.wikipedia.org/wiki/Type_inference). But in all that cases, we should just fall back to Object or throw. I don't know Daniel's intentions, but for me type inference for methods (same as for fields/variables) should only be used for simple, obvious cases, not for complex ones (eveb if these are of course the only interesting intellectual challnge ;-) ). If I, as a developer, cannot see the deduced type at a a glance, then the RHS expression is already too complex.

I have added some comments to the examples:

On 05.09.2018 23:03, Jochen Theodorou wrote:

private foo() {
  return new SomeMarkerInterface(){
    def x() {1}
  }
}

is the return type here SomeMarkerInterface or XYZ$1?

XYZ$1

Can I do foo().x()?

yes


private foo() {
  if (something) {
     return x // of class X
  } else {
     return y // of class Y
  }
}

if X and Y implement the interfaces Foo and Bar the common super type would be something like Object+Foo+Bar, which cannot be an actual return type, because the Java type system cannot properly express that type. Which is it then? Object, or Foo or Bar?

Intuitively I would not infer on interfaces, but only classes. In practice I would expect X and Y to have a common superclass that is not Object; otherwise infer Object.

And if you think this problem is small, you have to consider this one here as well:

private foo() {
  def ret
  if (something) {
     ret = x // of class X
  } else {
     ret = y // of class Y
  }
  return ret
}

Same problem as before obviously, just showing that using local variables makes it even worse.

Infer Object - if you use Object for the return variable type, this is what you should expect...



And how about this one?

private f(List<X> l) {   if (l.size()==1) {
    return l[0]
  } else {
    return f(l.tail())
  }
}

for me it is obvious the return type is X, but a compiler must be able to see through the recursive call and it must see that it is a recursive call.

Too complex => infer Object


private f(List<X> l) {   if (l.size()==1) {
    return g(l[0])
  } else {
    return g(f(l.tail()))
  }
}
private g(X x){x}
private g(List<X> l){l}


here it gets even more complicated... g(l[0]) is easy, that will return X, since l[0] will return X causing a call to g(X):X. But since we currently infer f, we cannot simply know what f(l.tail()) will be, thus we cannot easily know if we call here g(X):X or g(List<X>):List<X>.

Way too complex and ambivalent => throw GroovyMethodReturnTypeInferenceComplexityError telling programmer he must supply the return type explicitely (optional: compiler parameter to infer Object in these cases)


Or let us say there is also g(Object):Object and let us assume we delete g(X):X. Then inferring the type above successfully means obviously to let f return Object. The change will go unnoticed in f and cause secondary errors in callers of f. In the worst case even quite far away from the callsite itself.

I do not follow: What secondary errors would that be ? How would they differ from errors that occur when the user explicitely supplies Object as return type ?


Wanting more?

private f(List<X> l) {   if (l.size()==1) {
    return l[0]
  } else {
    return g(l.tail())
  }
}
private g(List<X> l){f(l)}

to know the return type of f I need to know the return type of g, for which I need to know the return type of f... global type inference could solve such problems, but makes about everything else more complicated

GroovyMethodReturnTypeInferenceComplexityError...

Cheers,
mg







Reply via email to