Bad interaction between wildcard and functional interface conversion
Hi all, The way the conversion between a lambda (or a method reference) and a functional interface is specified doesn't take wildcard (exactly ? super) into account making the concept of contravariance of functional interface less intuitive that it should be. The following code compiles: private static void create(Consumer<Consumer<String>> consumer) { consumer.accept(s -> System.out.println(s)); } This one doesn't compile because "? super Consumer<? super String>" is not a functional interface: private static void create2(Consumer<? super Consumer<? super String>> consumer) { consumer.accept(s -> System.out.println(s)); } The workaround is to introduce a cast :( private static void create3(Consumer<? super Consumer<? super String>> consumer) { consumer.accept((Consumer<String>)s -> System.out.println(s)); } which is stupid in this case because there is no ambiguity. This cast is just here because the JLS doesn't consider that ? super Consumer<...> is a valid target type IMO, this bug is very similar to JDK-6964923 and i think the spec should be changed to allow ? super Foo to be a valid target type for a lambda conversion (obviously if Foo is a functional interface). regards, Rémi
Bitten again by the very same issue :( The following code doesn't compile: static <K, T> Function<K, T> factory(Consumer<? super BiConsumer<? super K, ? super T>> consumer, Function<? super K, ? extends T> ifAbsent) { HashMap<K, T> map = new HashMap<>(); consumer.accept(map::put); return key -> map.computeIfAbsent(key, ifAbsent); } I really think that it's a serious bug, the only workaround is to not use wildcards correctly, i.e. <K, T> Function<K, T> factory(Consumer<BiConsumer<? super K, ? super T>> consumer, Function<? super K, ? extends T> ifAbsent) cheers, Rémi On 05/27/2015 05:29 PM, Remi Forax wrote:
Hi all,
The way the conversion between a lambda (or a method reference) and a functional interface is specified doesn't take wildcard (exactly ? super) into account making the concept of contravariance of functional interface less intuitive that it should be.
The following code compiles: private static void create(Consumer<Consumer<String>> consumer) { consumer.accept(s -> System.out.println(s)); }
This one doesn't compile because "? super Consumer<? super String>" is not a functional interface: private static void create2(Consumer<? super Consumer<? super String>> consumer) { consumer.accept(s -> System.out.println(s)); }
The workaround is to introduce a cast :( private static void create3(Consumer<? super Consumer<? super String>> consumer) { consumer.accept((Consumer<String>)s -> System.out.println(s)); } which is stupid in this case because there is no ambiguity. This cast is just here because the JLS doesn't consider that ? super Consumer<...> is a valid target type
IMO, this bug is very similar to JDK-6964923 and i think the spec should be changed to allow ? super Foo to be a valid target type for a lambda conversion (obviously if Foo is a functional interface).
regards, Rémi
Hi Remi, I think it's fair to say that I know my share of Java Generics, so I generally understand the motivation of introducing wildcards into method signatures. Just in your particular case (and in your original example), I don't see what you gain by having the "super" wildcard for the outer Consumer's type parameter. If you leave it out, the code compiles without problems: static <K, T> Function<K, T> factory(Consumer<BiConsumer<? super K, ? super T>> consumer, Function<? super K, ? extends T> ifAbsent) { ... } Can you enlighten me? Cheers, Gernot Am 29.06.2015 15:49, schrieb Remi Forax:
Bitten again by the very same issue :(
The following code doesn't compile: static <K, T> Function<K, T> factory(Consumer<? super BiConsumer<? super K, ? super T>> consumer, Function<? super K, ? extends T> ifAbsent) { HashMap<K, T> map = new HashMap<>(); consumer.accept(map::put); return key -> map.computeIfAbsent(key, ifAbsent); }
I really think that it's a serious bug, the only workaround is to not use wildcards correctly, i.e. <K, T> Function<K, T> factory(Consumer<BiConsumer<? super K, ? super T>> consumer, Function<? super K, ? extends T> ifAbsent)
cheers, Rémi
On 05/27/2015 05:29 PM, Remi Forax wrote:
Hi all,
The way the conversion between a lambda (or a method reference) and a functional interface is specified doesn't take wildcard (exactly ? super) into account making the concept of contravariance of functional interface less intuitive that it should be.
The following code compiles: private static void create(Consumer<Consumer<String>> consumer) { consumer.accept(s -> System.out.println(s)); }
This one doesn't compile because "? super Consumer<? super String>" is not a functional interface: private static void create2(Consumer<? super Consumer<? super String>> consumer) { consumer.accept(s -> System.out.println(s)); }
The workaround is to introduce a cast :( private static void create3(Consumer<? super Consumer<? super String>> consumer) { consumer.accept((Consumer<String>)s -> System.out.println(s)); } which is stupid in this case because there is no ambiguity. This cast is just here because the JLS doesn't consider that ? super Consumer<...> is a valid target type
IMO, this bug is very similar to JDK-6964923 and i think the spec should be changed to allow ? super Foo to be a valid target type for a lambda conversion (obviously if Foo is a functional interface).
regards, Rémi
On 07/05/2015 03:13 PM, Gernot Neppert wrote:
Hi Remi,
I think it's fair to say that I know my share of Java Generics, so I generally understand the motivation of introducing wildcards into method signatures. Just in your particular case (and in your original example), I don't see what you gain by having the "super" wildcard for the outer Consumer's type parameter. If you leave it out, the code compiles without problems:
static <K, T> Function<K, T> factory(Consumer<BiConsumer<? super K, ? super T>> consumer, Function<? super K, ? extends T> ifAbsent) { ... }
Can you enlighten me?
yes I can. The java.util.function.Consumer (or its method accept if you prefer) acts as a consumer of T ( :) ) so using the PECS rule [1], it should be a Consumer<? super ...>. The thing is that if you have a functional interface which is parametrized, if the functional interface is parametrized by a wildcard, it stops to be a functional interface because you can not do any lambda/method reference conversion (without adding an useless cast, that will be removed in the generated code BTW). So it's a stupid bug and I'm ashamed to haven't thought about it before the release of 8.
Cheers, Gernot
regards, Rémi [1] https://sites.google.com/site/io/effective-java-reloaded
Am 29.06.2015 15:49, schrieb Remi Forax:
Bitten again by the very same issue :(
The following code doesn't compile: static <K, T> Function<K, T> factory(Consumer<? super BiConsumer<? super K, ? super T>> consumer, Function<? super K, ? extends T> ifAbsent) { HashMap<K, T> map = new HashMap<>(); consumer.accept(map::put); return key -> map.computeIfAbsent(key, ifAbsent); }
I really think that it's a serious bug, the only workaround is to not use wildcards correctly, i.e. <K, T> Function<K, T> factory(Consumer<BiConsumer<? super K, ? super T>> consumer, Function<? super K, ? extends T> ifAbsent)
cheers, Rémi
On 05/27/2015 05:29 PM, Remi Forax wrote:
Hi all,
The way the conversion between a lambda (or a method reference) and a functional interface is specified doesn't take wildcard (exactly ? super) into account making the concept of contravariance of functional interface less intuitive that it should be.
The following code compiles: private static void create(Consumer<Consumer<String>> consumer) { consumer.accept(s -> System.out.println(s)); }
This one doesn't compile because "? super Consumer<? super String>" is not a functional interface: private static void create2(Consumer<? super Consumer<? super String>> consumer) { consumer.accept(s -> System.out.println(s)); }
The workaround is to introduce a cast :( private static void create3(Consumer<? super Consumer<? super String>> consumer) { consumer.accept((Consumer<String>)s -> System.out.println(s)); } which is stupid in this case because there is no ambiguity. This cast is just here because the JLS doesn't consider that ? super Consumer<...> is a valid target type
IMO, this bug is very similar to JDK-6964923 and i think the spec should be changed to allow ? super Foo to be a valid target type for a lambda conversion (obviously if Foo is a functional interface).
regards, Rémi
participants (2)
-
Gernot Neppert
-
Remi Forax