=?utf-8?Q?Re:_Overload_resolution_chooses_type_that's_invalid, _too_genera?= l or not at all
Timo Kinnunen
timo.kinnunen at gmail.com
Sun Jan 12 02:05:56 PST 2014
Hi,
As an addendum of sorts, I have done some exhaustive black-box testing of overload resolution and can now share some numbers. Bottom line: with an API supporting 3 functional types, 1 of which throws checked exceptions, there is only a 3% chance, ~22 out of 728 valid API designs, that all of these simple calls compile against that API:
class Test___U1__C4____ extends ObjectOverloadTests {
static OverloadCombo___U1__C4____<String, IOException> stream() { return st___U1__C4____; }
public static void useCase_1_1_1() throws IOException, N {
Consumer<String> c = (String s) -> System.out.println(s);
stream().forEach(c);
}
public static void useCase_1_1_2() throws IOException, YN, N {
stream().forEach((String s) -> System.out.println(s));
}
public static void useCase_1_1_3() throws IOException, YN, N {
stream().forEach(Refs::ref1);
}
public static void useCase_2_1_1() throws IOException, Y {
UserE<String, IOException> c = (String s) -> System.out.println(Files.size(Paths.get(s)));
stream().forEach(c);
}
public static void useCase_2_1_2() throws IOException, YN, Y {
stream().forEach((String s) -> System.out.println(Files.size(Paths.get(s))));
}
public static void useCase_2_1_3() throws IOException, YN, Y {
stream().forEach(Refs::ref2);
} }
Needless to say, those are as basic function calls as you can get so a 3% success rate for them is really bad. And there’s more. By adding a wildcard bound option I can double the number of available variations 3 times, once per each base type. A UserE<T, RuntimeException> option doubles the number one more time. That’s now 50624 (!) different ways one could try to write this API.
Notice those YN, Y and N exception specifications. They are there to allow the calling code to “guess” which overload they think they should be calling and see a compile-time warning go away when they guess the right one. No such luck so far, in Eclipse and ECJ or in Netbeans and javac. The base YN is there as safety to not pollute the results with more errors.
For those interested, here are the building blocks from which the overload combinations were picked, again only valid combinations were picked, all compiled without errors in their code:
public static class SC1<T, E extends Throwable> extends BaseE<T, E> { SC1 c1_; /**********/ /***/ public /*********************/ void forEach(Consumer<T> /**/ c1) throws E, N { allC(resultN(), c1); } }
public static class SC2<T, E extends Throwable> extends BaseE<T, E> { SC2 c1_; /**********/ /***/ public <C extends Consumer<T>> void forEach(C/********/ /**/ c1) throws E, N { allC(resultN(), c1); } }
public static class SU1<T, E extends Throwable> extends BaseE<T, E> { SU1 u1_; /**********/ /***/ public /*********************/ void forEach(UserE<T, E> /**/ u1) throws E, Y { allU(resultY(), u1); } }
public static class SU2<T, E extends Throwable> extends BaseE<T, E> { SU2 u1_; /**********/ /***/ public <U extends UserE<T, E>> void forEach(U/********/ /**/ u1) throws E, Y { allU(resultY(), u1); } }
public static class SO1<T, E extends Throwable> extends BaseE<T, E> { SO1 o1_; /**********/ /***/ public /*********************/ void forEach(OtherUse<T> /**/ o1) throws E, N { allO(resultN(), o1); } }
public static class SO2<T, E extends Throwable> extends BaseE<T, E> { SO2 o1_; /**********/ /***/ public <O extends OtherUse<T>> void forEach(O/********/ /**/ o1) throws E, N { allO(resultN(), o1); } }
public static class SC3<T, E extends Throwable> extends BaseE<T, E> { SC3 cn_; @SafeVarargs final public /*********************/ void forEach(Consumer<T> ... cn) throws E, N { allC(resultN(), cn); } }
public static class SC4<T, E extends Throwable> extends BaseE<T, E> { SC4 cn_; @SafeVarargs final public <C extends Consumer<T>> void forEach(C/********/ ... cn) throws E, N { allC(resultN(), cn); } }
public static class SU3<T, E extends Throwable> extends BaseE<T, E> { SU3 un_; @SafeVarargs final public /*********************/ void forEach(UserE<T, E> ... un) throws E, Y { allU(resultY(), un); } }
public static class SU4<T, E extends Throwable> extends BaseE<T, E> { SU4 un_; @SafeVarargs final public <U extends UserE<T, E>> void forEach(U/********/ ... un) throws E, Y { allU(resultY(), un); } }
public static class SO3<T, E extends Throwable> extends BaseE<T, E> { SO3 on_; @SafeVarargs final public /*********************/ void forEach(OtherUse<T> ... on) throws E, N { allO(resultN(), on); } }
public static class SO4<T, E extends Throwable> extends BaseE<T, E> { SO4 on_; @SafeVarargs final public <O extends OtherUse<T>> void forEach(O/********/ ... on) throws E, N { allO(resultN(), on); } }
--
Have a nice day,
Timo.
Sent from Windows Mail
From: Timo Kinnunen
Sent: Friday, January 10, 2014 13:05
To: lambda-dev at openjdk.java.net
Hi,
In the case where the difference between two target types is only that one of them has a generic throws declaration, this leads to ambiguity even when choosing the wrong one would be a compile error.
This seems to be working as specified, with the goal to make the result more stable and predictable, but I would argue the actual result is just the opposite. Why not try each candidate in turn and choose the one that throws the least?
I have found a workaround. By making some small artificial changes (notice a pattern?) to one method that makes its signature less readable I can affect changes in which rounds each method participates. This allows things to compile but I lose some useful information about which lambdas actually don't throw exceptions. I have found a lot of variations to choose from but not the one that produces the optimal result so far.
I'd like the compiler to choose the optimal method automatically, because it knows the types better than I do. Absent that, I'd like it to give each method its due consideration, because that's why I added the overloads in the first place. Absent that, I'd like some API for saying which order the overloaded methods should be tried. And absent that, I'd like to have some official documentation on how to correctly target methods to a particular round of resolution.
Could I have one of those, please?
Sent from my Windows Phone
More information about the lambda-dev
mailing list