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