Issues with generic type detection of SAM types implemented using lambdas

classic Classic list List threaded Threaded
18 messages Options
Reply | Threaded
Open this post in threaded view
|

Issues with generic type detection of SAM types implemented using lambdas

Oliver Gierke-2
Hi all,

Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise as some high-level API taking a SAM type as parameter is usually an indicator to users, that they can be used with Lambdas. If the object passed in is then inspected for generic types somewhere down the call stack this causes issues. Handing in a dedicated implementation of the SAM type is a workaround bit I think that's highly confusing and can be a source of errors hard to understand and debug.

I've added an example below.

Cheers,
Ollie

public class LambdaTypeDetectionSample {

        public static void main(String[] args) {

                Function<Integer, String> lambdaFunction = i -> i.toString();
                Function<Integer, String> oldschoolFunction = new Function<Integer, String>() {

                        public String apply(Integer t) {
                                return t.toString();
                        }
                };

                printTypeArguments(oldschoolFunction);

                // Yields:
                // java.util.function.Function<java.lang.Integer, java.lang.String> is a class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
                // class java.lang.Integer
                // class java.lang.String

                printTypeArguments(lambdaFunction);

                // Yields:
                // interface java.util.function.Function is a class java.lang.Class
        }

        private static void printTypeArguments(Function<?, ?> function) {

                Type type = function.getClass().getGenericInterfaces()[0];

                System.out.println(type + " is a " + type.getClass());

                if (type instanceof ParameterizedType) {

                        ParameterizedType functionInterface = (ParameterizedType) type;
                        Arrays.stream(functionInterface.getActualTypeArguments()).forEach(System.out::println);
                }
        }
}

signature.asc (858 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

Oliver Gierke-2
Hi again,

didn't get much response but it looks like there are some improvements for lambdas planned for at least JDK10 [0]. Is what I asked for below maybe a candidate for inclusion into those efforts?

Is there maybe a better place to ask for this?

Cheers,
Ollie

[0] https://www.infoq.com/news/2017/01/java10-lambda-leftovers

> Am 23.12.2016 um 12:22 schrieb Oliver Gierke <[hidden email]>:
>
> Hi all,
>
> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise as some high-level API taking a SAM type as parameter is usually an indicator to users, that they can be used with Lambdas. If the object passed in is then inspected for generic types somewhere down the call stack this causes issues. Handing in a dedicated implementation of the SAM type is a workaround bit I think that's highly confusing and can be a source of errors hard to understand and debug.
>
> I've added an example below.
>
> Cheers,
> Ollie
>
> public class LambdaTypeDetectionSample {
>
> public static void main(String[] args) {
>
> Function<Integer, String> lambdaFunction = i -> i.toString();
> Function<Integer, String> oldschoolFunction = new Function<Integer, String>() {
>
> public String apply(Integer t) {
> return t.toString();
> }
> };
>
> printTypeArguments(oldschoolFunction);
>
> // Yields:
> // java.util.function.Function<java.lang.Integer, java.lang.String> is a class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
> // class java.lang.Integer
> // class java.lang.String
>
> printTypeArguments(lambdaFunction);
>
> // Yields:
> // interface java.util.function.Function is a class java.lang.Class
> }
>
> private static void printTypeArguments(Function<?, ?> function) {
>
> Type type = function.getClass().getGenericInterfaces()[0];
>
> System.out.println(type + " is a " + type.getClass());
>
> if (type instanceof ParameterizedType) {
>
> ParameterizedType functionInterface = (ParameterizedType) type;
> Arrays.stream(functionInterface.getActualTypeArguments()).forEach(System.out::println);
> }
> }
> }


signature.asc (858 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

Remi Forax
Hi Oliver,
sorry for not responding earlier,
not having the generic type information was something discussed by the lambda export group, and i'm sure you can also find several threads about that subject on lambda-dev.

There are two reasons to not generate the generic info at runtime:
- these info are used to enable separate compilation, because of the erasure, the argument of the parameterized types is lost, so at the method boundary, the compiler has to keep the generic information to enable separate compilation. At runtime, when the class that hosts a lambda is generated, there is no need for such information.
- a lambda at runtime should be as lightweight as possible (at least for non serializable lambdas), so the spec allows to have several lambdas that share the same class at runtime, in that context having one generic information for several lambdas make no sense.

regards,
Rémi

----- Mail original -----
> De: "Oliver Gierke" <[hidden email]>
> À: [hidden email]
> Envoyé: Mercredi 18 Janvier 2017 19:06:30
> Objet: Re: Issues with generic type detection of SAM types implemented using lambdas

> Hi again,
>
> didn't get much response but it looks like there are some improvements for
> lambdas planned for at least JDK10 [0]. Is what I asked for below maybe a
> candidate for inclusion into those efforts?
>
> Is there maybe a better place to ask for this?
>
> Cheers,
> Ollie
>
> [0] https://www.infoq.com/news/2017/01/java10-lambda-leftovers
>
>> Am 23.12.2016 um 12:22 schrieb Oliver Gierke <[hidden email]>:
>>
>> Hi all,
>>
>> Lambda based implementations of SAM types currently don't support inspecting the
>> type for generic type parameters. This can cause unexpected surprise as some
>> high-level API taking a SAM type as parameter is usually an indicator to users,
>> that they can be used with Lambdas. If the object passed in is then inspected
>> for generic types somewhere down the call stack this causes issues. Handing in
>> a dedicated implementation of the SAM type is a workaround bit I think that's
>> highly confusing and can be a source of errors hard to understand and debug.
>>
>> I've added an example below.
>>
>> Cheers,
>> Ollie
>>
>> public class LambdaTypeDetectionSample {
>>
>> public static void main(String[] args) {
>>
>> Function<Integer, String> lambdaFunction = i -> i.toString();
>> Function<Integer, String> oldschoolFunction = new Function<Integer, String>() {
>>
>> public String apply(Integer t) {
>> return t.toString();
>> }
>> };
>>
>> printTypeArguments(oldschoolFunction);
>>
>> // Yields:
>> // java.util.function.Function<java.lang.Integer, java.lang.String> is a class
>> sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
>> // class java.lang.Integer
>> // class java.lang.String
>>
>> printTypeArguments(lambdaFunction);
>>
>> // Yields:
>> // interface java.util.function.Function is a class java.lang.Class
>> }
>>
>> private static void printTypeArguments(Function<?, ?> function) {
>>
>> Type type = function.getClass().getGenericInterfaces()[0];
>>
>> System.out.println(type + " is a " + type.getClass());
>>
>> if (type instanceof ParameterizedType) {
>>
>> ParameterizedType functionInterface = (ParameterizedType) type;
>> Arrays.stream(functionInterface.getActualTypeArguments()).forEach(System.out::println);
>> }
>> }
> > }
Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

Maurizio Cimadamore
In reply to this post by Oliver Gierke-2
Hi Oliver,
this request seems to be similar to other requests expressed in the past:

Signature attribute on lambda method:

http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009220.html

Annotations on lambdas:

http://mail.openjdk.java.net/pipermail/compiler-dev/2015-July/009662.html


The recurring theme is that we don't want to make promises on
implementation details - that is how is a lambda implemented. For now, a
lambda is implemented as a (dynamically generated) anonymous class
instance - so certain requests seem to make sense. But as the
implementation will get better at sharing, having to expose details such
as signature attributes and such will become an obstacle to further
improvements.

For instance, the runtime does NOT have to distinguish between a
Supplier<String> and a Supplier<Object> - example:

class Test {
    void test() {
       Supplier<String> ss = this::m;
       Supplier<Object> si = this::m;
    }

    String m() { return ""; }
}

This gives the following bytceode:

void test();
     descriptor: ()V
     flags:
     Code:
       stack=1, locals=3, args_size=1
          0: aload_0
          1: invokedynamic #2,  0              // InvokeDynamic
#0:get:(LTest;)Ljava/util/function/Supplier;
          6: astore_1
          7: aload_0
          8: invokedynamic #3,  0              // InvokeDynamic
#1:get:(LTest;)Ljava/util/function/Supplier;
         13: astore_2
         14: return

BootstrapMethods:
   0: #18 invokestatic
java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
     Method arguments:
       #19 ()Ljava/lang/Object;
       #20 invokevirtual Test.m:()Ljava/lang/String;
       #21 ()Ljava/lang/String;
   1: #18 invokestatic
java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
     Method arguments:
       #19 ()Ljava/lang/Object;
       #20 invokevirtual Test.m:()Ljava/lang/String;
       #19 ()Ljava/lang/Object;


As you can see, both invokedynamic share the same SAM type - the erased
java/util/function/Supplier. The only difference between the two indys
is the method type passed as 3rd static argument - in one case ()String
is passed, in the other ()Object is passed - that dictates the signature
of the dynamically generated method implementing the sam type.

In other words, if the lambda metafactory used the same instance for
both method references, everything would still work (in fact, the
generated bytecode is the same for both classes!).

Maurizio



On 18/01/17 18:06, Oliver Gierke wrote:

> Hi again,
>
> didn't get much response but it looks like there are some improvements for lambdas planned for at least JDK10 [0]. Is what I asked for below maybe a candidate for inclusion into those efforts?
>
> Is there maybe a better place to ask for this?
>
> Cheers,
> Ollie
>
> [0] https://www.infoq.com/news/2017/01/java10-lambda-leftovers
>
>> Am 23.12.2016 um 12:22 schrieb Oliver Gierke <[hidden email]>:
>>
>> Hi all,
>>
>> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise as some high-level API taking a SAM type as parameter is usually an indicator to users, that they can be used with Lambdas. If the object passed in is then inspected for generic types somewhere down the call stack this causes issues. Handing in a dedicated implementation of the SAM type is a workaround bit I think that's highly confusing and can be a source of errors hard to understand and debug.
>>
>> I've added an example below.
>>
>> Cheers,
>> Ollie
>>
>> public class LambdaTypeDetectionSample {
>>
>> public static void main(String[] args) {
>>
>> Function<Integer, String> lambdaFunction = i -> i.toString();
>> Function<Integer, String> oldschoolFunction = new Function<Integer, String>() {
>>
>> public String apply(Integer t) {
>> return t.toString();
>> }
>> };
>>
>> printTypeArguments(oldschoolFunction);
>>
>> // Yields:
>> // java.util.function.Function<java.lang.Integer, java.lang.String> is a class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
>> // class java.lang.Integer
>> // class java.lang.String
>>
>> printTypeArguments(lambdaFunction);
>>
>> // Yields:
>> // interface java.util.function.Function is a class java.lang.Class
>> }
>>
>> private static void printTypeArguments(Function<?, ?> function) {
>>
>> Type type = function.getClass().getGenericInterfaces()[0];
>>
>> System.out.println(type + " is a " + type.getClass());
>>
>> if (type instanceof ParameterizedType) {
>>
>> ParameterizedType functionInterface = (ParameterizedType) type;
>> Arrays.stream(functionInterface.getActualTypeArguments()).forEach(System.out::println);
>> }
>> }
>> }

Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

Oliver Gierke-2
Hi Maurizio,

thanks again for you in-depth explanation. I get all that and the design considerations behind the decisions that were made. I just wanted to point out that an ordinary user -- in general client code that uses APIs with SAM types -- *has to implicitly know* whether *any code* (potentially Java 6 based) downstream might attempt generics resolution before she can decide whether to use a Lambda with this SAM type.

I think that's a very fundamental usability problem because it doesn't even need some arbitrary corner case. If there's a piece of code attempting the resolution anywhere downstream, it just won't work. Never. The only similar scenario (in terms of some parts of the reflection API not working as others) I remember is debug symbols on interface methods. That was changed in Java 8 with the -parameters flag. So I thought, it might be worth bringing this one up here as maybe the context has or will have changed for Java 9 / 10.

Cheers,
Ollie

> Am 19.01.2017 um 02:52 schrieb Maurizio Cimadamore <[hidden email]>:
>
> Hi Oliver,
> this request seems to be similar to other requests expressed in the past:
>
> Signature attribute on lambda method:
>
> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009220.html
>
> Annotations on lambdas:
>
> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-July/009662.html
>
>
> The recurring theme is that we don't want to make promises on implementation details - that is how is a lambda implemented. For now, a lambda is implemented as a (dynamically generated) anonymous class instance - so certain requests seem to make sense. But as the implementation will get better at sharing, having to expose details such as signature attributes and such will become an obstacle to further improvements.
>
> For instance, the runtime does NOT have to distinguish between a Supplier<String> and a Supplier<Object> - example:
>
> class Test {
>   void test() {
>      Supplier<String> ss = this::m;
>      Supplier<Object> si = this::m;
>   }
>
>   String m() { return ""; }
> }
>
> This gives the following bytceode:
>
> void test();
>    descriptor: ()V
>    flags:
>    Code:
>      stack=1, locals=3, args_size=1
>         0: aload_0
>         1: invokedynamic #2,  0              // InvokeDynamic #0:get:(LTest;)Ljava/util/function/Supplier;
>         6: astore_1
>         7: aload_0
>         8: invokedynamic #3,  0              // InvokeDynamic #1:get:(LTest;)Ljava/util/function/Supplier;
>        13: astore_2
>        14: return
>
> BootstrapMethods:
>  0: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>    Method arguments:
>      #19 ()Ljava/lang/Object;
>      #20 invokevirtual Test.m:()Ljava/lang/String;
>      #21 ()Ljava/lang/String;
>  1: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>    Method arguments:
>      #19 ()Ljava/lang/Object;
>      #20 invokevirtual Test.m:()Ljava/lang/String;
>      #19 ()Ljava/lang/Object;
>
>
> As you can see, both invokedynamic share the same SAM type - the erased java/util/function/Supplier. The only difference between the two indys is the method type passed as 3rd static argument - in one case ()String is passed, in the other ()Object is passed - that dictates the signature of the dynamically generated method implementing the sam type.
>
> In other words, if the lambda metafactory used the same instance for both method references, everything would still work (in fact, the generated bytecode is the same for both classes!).
>
> Maurizio
>
>
>
> On 18/01/17 18:06, Oliver Gierke wrote:
>> Hi again,
>>
>> didn't get much response but it looks like there are some improvements for lambdas planned for at least JDK10 [0]. Is what I asked for below maybe a candidate for inclusion into those efforts?
>>
>> Is there maybe a better place to ask for this?
>>
>> Cheers,
>> Ollie
>>
>> [0] https://www.infoq.com/news/2017/01/java10-lambda-leftovers
>>
>>> Am 23.12.2016 um 12:22 schrieb Oliver Gierke <[hidden email]>:
>>>
>>> Hi all,
>>>
>>> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise as some high-level API taking a SAM type as parameter is usually an indicator to users, that they can be used with Lambdas. If the object passed in is then inspected for generic types somewhere down the call stack this causes issues. Handing in a dedicated implementation of the SAM type is a workaround bit I think that's highly confusing and can be a source of errors hard to understand and debug.
>>>
>>> I've added an example below.
>>>
>>> Cheers,
>>> Ollie
>>>
>>> public class LambdaTypeDetectionSample {
>>>
>>> public static void main(String[] args) {
>>>
>>> Function<Integer, String> lambdaFunction = i -> i.toString();
>>> Function<Integer, String> oldschoolFunction = new Function<Integer, String>() {
>>>
>>> public String apply(Integer t) {
>>> return t.toString();
>>> }
>>> };
>>>
>>> printTypeArguments(oldschoolFunction);
>>>
>>> // Yields:
>>> // java.util.function.Function<java.lang.Integer, java.lang.String> is a class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
>>> // class java.lang.Integer
>>> // class java.lang.String
>>>
>>> printTypeArguments(lambdaFunction);
>>>
>>> // Yields:
>>> // interface java.util.function.Function is a class java.lang.Class
>>> }
>>>
>>> private static void printTypeArguments(Function<?, ?> function) {
>>>
>>> Type type = function.getClass().getGenericInterfaces()[0];
>>>
>>> System.out.println(type + " is a " + type.getClass());
>>>
>>> if (type instanceof ParameterizedType) {
>>>
>>> ParameterizedType functionInterface = (ParameterizedType) type;
>>> Arrays.stream(functionInterface.getActualTypeArguments()).forEach(System.out::println);
>>> }
>>> }
>>> }
>


signature.asc (858 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

Oliver Gierke-2
Hi all,

I hate to bring this up again but as the adoption of Java 8 is growing significantly, this problem now comes up at least every other week and I can really understand the pain of our users. Find one example here [0].

Is there really no chance this is ever going to change?

Cheers,
Ollie

[0] https://github.com/spring-projects/spring-hateoas/issues/688

> Am 19.01.2017 um 08:40 schrieb Oliver Gierke <[hidden email]>:
>
> Hi Maurizio,
>
> thanks again for you in-depth explanation. I get all that and the design considerations behind the decisions that were made. I just wanted to point out that an ordinary user -- in general client code that uses APIs with SAM types -- *has to implicitly know* whether *any code* (potentially Java 6 based) downstream might attempt generics resolution before she can decide whether to use a Lambda with this SAM type.
>
> I think that's a very fundamental usability problem because it doesn't even need some arbitrary corner case. If there's a piece of code attempting the resolution anywhere downstream, it just won't work. Never. The only similar scenario (in terms of some parts of the reflection API not working as others) I remember is debug symbols on interface methods. That was changed in Java 8 with the -parameters flag. So I thought, it might be worth bringing this one up here as maybe the context has or will have changed for Java 9 / 10.
>
> Cheers,
> Ollie
>
>> Am 19.01.2017 um 02:52 schrieb Maurizio Cimadamore <[hidden email]>:
>>
>> Hi Oliver,
>> this request seems to be similar to other requests expressed in the past:
>>
>> Signature attribute on lambda method:
>>
>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009220.html
>>
>> Annotations on lambdas:
>>
>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-July/009662.html
>>
>>
>> The recurring theme is that we don't want to make promises on implementation details - that is how is a lambda implemented. For now, a lambda is implemented as a (dynamically generated) anonymous class instance - so certain requests seem to make sense. But as the implementation will get better at sharing, having to expose details such as signature attributes and such will become an obstacle to further improvements.
>>
>> For instance, the runtime does NOT have to distinguish between a Supplier<String> and a Supplier<Object> - example:
>>
>> class Test {
>>  void test() {
>>     Supplier<String> ss = this::m;
>>     Supplier<Object> si = this::m;
>>  }
>>
>>  String m() { return ""; }
>> }
>>
>> This gives the following bytceode:
>>
>> void test();
>>   descriptor: ()V
>>   flags:
>>   Code:
>>     stack=1, locals=3, args_size=1
>>        0: aload_0
>>        1: invokedynamic #2,  0              // InvokeDynamic #0:get:(LTest;)Ljava/util/function/Supplier;
>>        6: astore_1
>>        7: aload_0
>>        8: invokedynamic #3,  0              // InvokeDynamic #1:get:(LTest;)Ljava/util/function/Supplier;
>>       13: astore_2
>>       14: return
>>
>> BootstrapMethods:
>> 0: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>>   Method arguments:
>>     #19 ()Ljava/lang/Object;
>>     #20 invokevirtual Test.m:()Ljava/lang/String;
>>     #21 ()Ljava/lang/String;
>> 1: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>>   Method arguments:
>>     #19 ()Ljava/lang/Object;
>>     #20 invokevirtual Test.m:()Ljava/lang/String;
>>     #19 ()Ljava/lang/Object;
>>
>>
>> As you can see, both invokedynamic share the same SAM type - the erased java/util/function/Supplier. The only difference between the two indys is the method type passed as 3rd static argument - in one case ()String is passed, in the other ()Object is passed - that dictates the signature of the dynamically generated method implementing the sam type.
>>
>> In other words, if the lambda metafactory used the same instance for both method references, everything would still work (in fact, the generated bytecode is the same for both classes!).
>>
>> Maurizio
>>
>>
>>
>> On 18/01/17 18:06, Oliver Gierke wrote:
>>> Hi again,
>>>
>>> didn't get much response but it looks like there are some improvements for lambdas planned for at least JDK10 [0]. Is what I asked for below maybe a candidate for inclusion into those efforts?
>>>
>>> Is there maybe a better place to ask for this?
>>>
>>> Cheers,
>>> Ollie
>>>
>>> [0] https://www.infoq.com/news/2017/01/java10-lambda-leftovers
>>>
>>>> Am 23.12.2016 um 12:22 schrieb Oliver Gierke <[hidden email]>:
>>>>
>>>> Hi all,
>>>>
>>>> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise as some high-level API taking a SAM type as parameter is usually an indicator to users, that they can be used with Lambdas. If the object passed in is then inspected for generic types somewhere down the call stack this causes issues. Handing in a dedicated implementation of the SAM type is a workaround bit I think that's highly confusing and can be a source of errors hard to understand and debug.
>>>>
>>>> I've added an example below.
>>>>
>>>> Cheers,
>>>> Ollie
>>>>
>>>> public class LambdaTypeDetectionSample {
>>>>
>>>> public static void main(String[] args) {
>>>>
>>>> Function<Integer, String> lambdaFunction = i -> i.toString();
>>>> Function<Integer, String> oldschoolFunction = new Function<Integer, String>() {
>>>>
>>>> public String apply(Integer t) {
>>>> return t.toString();
>>>> }
>>>> };
>>>>
>>>> printTypeArguments(oldschoolFunction);
>>>>
>>>> // Yields:
>>>> // java.util.function.Function<java.lang.Integer, java.lang.String> is a class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
>>>> // class java.lang.Integer
>>>> // class java.lang.String
>>>>
>>>> printTypeArguments(lambdaFunction);
>>>>
>>>> // Yields:
>>>> // interface java.util.function.Function is a class java.lang.Class
>>>> }
>>>>
>>>> private static void printTypeArguments(Function<?, ?> function) {
>>>>
>>>> Type type = function.getClass().getGenericInterfaces()[0];
>>>>
>>>> System.out.println(type + " is a " + type.getClass());
>>>>
>>>> if (type instanceof ParameterizedType) {
>>>>
>>>> ParameterizedType functionInterface = (ParameterizedType) type;
>>>> Arrays.stream(functionInterface.getActualTypeArguments()).forEach(System.out::println);
>>>> }
>>>> }
>>>> }
>>
>


signature.asc (849 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

Maurizio Cimadamore
I think I still stand by the answers provided.

Brian's comment here is particularly insightful:

http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009268.html

E.g. it's not much that we're saying "works as designed" - it's more
that we see this as some kind of short-term relief that is unavoidably
cause much worse problems later down the road.

So, I see two options: we add all the required attributes/annotations,
and commit to never change the implementation strategy again (ouch!); or
we leave freedom for the implementation to come up with more efficient
runtime strategies - which unfortunately works against the use case you
are presenting.

That said, I agree with what's said in the evaluation you point at - the
fact that many tools out there (IDEs) suggest you to turn all anon inner
classes into lambdas might lead to people doing the refactoring w/o
really understanding the implications for the framework they're working on.

I wonder whether some other resolution - e.g. an explicit mechanism to
opt out of the functional interface business would help? E.g. if a
functional interface is marked with @NoFunctionalInterface (provisional
name of course), then the compiler (and the IDE) should refuse to
consider this interface a suitable target type for a lambda
expression/method reference. Would that mitigate the pain?

Maurizio

On 11/01/18 09:10, Oliver Gierke wrote:

> Hi all,
>
> I hate to bring this up again but as the adoption of Java 8 is growing significantly, this problem now comes up at least every other week and I can really understand the pain of our users. Find one example here [0].
>
> Is there really no chance this is ever going to change?
>
> Cheers,
> Ollie
>
> [0] https://github.com/spring-projects/spring-hateoas/issues/688
>
>> Am 19.01.2017 um 08:40 schrieb Oliver Gierke <[hidden email]>:
>>
>> Hi Maurizio,
>>
>> thanks again for you in-depth explanation. I get all that and the design considerations behind the decisions that were made. I just wanted to point out that an ordinary user -- in general client code that uses APIs with SAM types -- *has to implicitly know* whether *any code* (potentially Java 6 based) downstream might attempt generics resolution before she can decide whether to use a Lambda with this SAM type.
>>
>> I think that's a very fundamental usability problem because it doesn't even need some arbitrary corner case. If there's a piece of code attempting the resolution anywhere downstream, it just won't work. Never. The only similar scenario (in terms of some parts of the reflection API not working as others) I remember is debug symbols on interface methods. That was changed in Java 8 with the -parameters flag. So I thought, it might be worth bringing this one up here as maybe the context has or will have changed for Java 9 / 10.
>>
>> Cheers,
>> Ollie
>>
>>> Am 19.01.2017 um 02:52 schrieb Maurizio Cimadamore <[hidden email]>:
>>>
>>> Hi Oliver,
>>> this request seems to be similar to other requests expressed in the past:
>>>
>>> Signature attribute on lambda method:
>>>
>>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009220.html
>>>
>>> Annotations on lambdas:
>>>
>>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-July/009662.html
>>>
>>>
>>> The recurring theme is that we don't want to make promises on implementation details - that is how is a lambda implemented. For now, a lambda is implemented as a (dynamically generated) anonymous class instance - so certain requests seem to make sense. But as the implementation will get better at sharing, having to expose details such as signature attributes and such will become an obstacle to further improvements.
>>>
>>> For instance, the runtime does NOT have to distinguish between a Supplier<String> and a Supplier<Object> - example:
>>>
>>> class Test {
>>>   void test() {
>>>      Supplier<String> ss = this::m;
>>>      Supplier<Object> si = this::m;
>>>   }
>>>
>>>   String m() { return ""; }
>>> }
>>>
>>> This gives the following bytceode:
>>>
>>> void test();
>>>    descriptor: ()V
>>>    flags:
>>>    Code:
>>>      stack=1, locals=3, args_size=1
>>>         0: aload_0
>>>         1: invokedynamic #2,  0              // InvokeDynamic #0:get:(LTest;)Ljava/util/function/Supplier;
>>>         6: astore_1
>>>         7: aload_0
>>>         8: invokedynamic #3,  0              // InvokeDynamic #1:get:(LTest;)Ljava/util/function/Supplier;
>>>        13: astore_2
>>>        14: return
>>>
>>> BootstrapMethods:
>>> 0: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>>>    Method arguments:
>>>      #19 ()Ljava/lang/Object;
>>>      #20 invokevirtual Test.m:()Ljava/lang/String;
>>>      #21 ()Ljava/lang/String;
>>> 1: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>>>    Method arguments:
>>>      #19 ()Ljava/lang/Object;
>>>      #20 invokevirtual Test.m:()Ljava/lang/String;
>>>      #19 ()Ljava/lang/Object;
>>>
>>>
>>> As you can see, both invokedynamic share the same SAM type - the erased java/util/function/Supplier. The only difference between the two indys is the method type passed as 3rd static argument - in one case ()String is passed, in the other ()Object is passed - that dictates the signature of the dynamically generated method implementing the sam type.
>>>
>>> In other words, if the lambda metafactory used the same instance for both method references, everything would still work (in fact, the generated bytecode is the same for both classes!).
>>>
>>> Maurizio
>>>
>>>
>>>
>>> On 18/01/17 18:06, Oliver Gierke wrote:
>>>> Hi again,
>>>>
>>>> didn't get much response but it looks like there are some improvements for lambdas planned for at least JDK10 [0]. Is what I asked for below maybe a candidate for inclusion into those efforts?
>>>>
>>>> Is there maybe a better place to ask for this?
>>>>
>>>> Cheers,
>>>> Ollie
>>>>
>>>> [0] https://www.infoq.com/news/2017/01/java10-lambda-leftovers
>>>>
>>>>> Am 23.12.2016 um 12:22 schrieb Oliver Gierke <[hidden email]>:
>>>>>
>>>>> Hi all,
>>>>>
>>>>> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise as some high-level API taking a SAM type as parameter is usually an indicator to users, that they can be used with Lambdas. If the object passed in is then inspected for generic types somewhere down the call stack this causes issues. Handing in a dedicated implementation of the SAM type is a workaround bit I think that's highly confusing and can be a source of errors hard to understand and debug.
>>>>>
>>>>> I've added an example below.
>>>>>
>>>>> Cheers,
>>>>> Ollie
>>>>>
>>>>> public class LambdaTypeDetectionSample {
>>>>>
>>>>> public static void main(String[] args) {
>>>>>
>>>>> Function<Integer, String> lambdaFunction = i -> i.toString();
>>>>> Function<Integer, String> oldschoolFunction = new Function<Integer, String>() {
>>>>>
>>>>> public String apply(Integer t) {
>>>>> return t.toString();
>>>>> }
>>>>> };
>>>>>
>>>>> printTypeArguments(oldschoolFunction);
>>>>>
>>>>> // Yields:
>>>>> // java.util.function.Function<java.lang.Integer, java.lang.String> is a class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
>>>>> // class java.lang.Integer
>>>>> // class java.lang.String
>>>>>
>>>>> printTypeArguments(lambdaFunction);
>>>>>
>>>>> // Yields:
>>>>> // interface java.util.function.Function is a class java.lang.Class
>>>>> }
>>>>>
>>>>> private static void printTypeArguments(Function<?, ?> function) {
>>>>>
>>>>> Type type = function.getClass().getGenericInterfaces()[0];
>>>>>
>>>>> System.out.println(type + " is a " + type.getClass());
>>>>>
>>>>> if (type instanceof ParameterizedType) {
>>>>>
>>>>> ParameterizedType functionInterface = (ParameterizedType) type;
>>>>> Arrays.stream(functionInterface.getActualTypeArguments()).forEach(System.out::println);
>>>>> }
>>>>> }
>>>>> }

Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

Oliver Gierke-2
The problem with that approach is that whether Lambda usage is working does not depend on the declared interface but whether other code - potentially living somewhere completely else - is attempting generics resolution. I.e. the interface designer might not know up front.

In cases where the interface provider also is in charge of the processing code, the special annotation would certainly help.

I am planning to add a bit of defensive code to the processing side of things to reject runtime instances that lack the required generics information. Is there a canonical resource besides this mailing list thread I could refer to to explain why we need to reject those instances (a section in the spec or the like)?

Cheers,
Ollie

> Am 11.01.2018 um 12:18 schrieb Maurizio Cimadamore <[hidden email]>:
>
> I think I still stand by the answers provided.
>
> Brian's comment here is particularly insightful:
>
> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009268.html
>
> E.g. it's not much that we're saying "works as designed" - it's more that we see this as some kind of short-term relief that is unavoidably cause much worse problems later down the road.
>
> So, I see two options: we add all the required attributes/annotations, and commit to never change the implementation strategy again (ouch!); or we leave freedom for the implementation to come up with more efficient runtime strategies - which unfortunately works against the use case you are presenting.
>
> That said, I agree with what's said in the evaluation you point at - the fact that many tools out there (IDEs) suggest you to turn all anon inner classes into lambdas might lead to people doing the refactoring w/o really understanding the implications for the framework they're working on.
>
> I wonder whether some other resolution - e.g. an explicit mechanism to opt out of the functional interface business would help? E.g. if a functional interface is marked with @NoFunctionalInterface (provisional name of course), then the compiler (and the IDE) should refuse to consider this interface a suitable target type for a lambda expression/method reference. Would that mitigate the pain?
>
> Maurizio
>
> On 11/01/18 09:10, Oliver Gierke wrote:
>> Hi all,
>>
>> I hate to bring this up again but as the adoption of Java 8 is growing significantly, this problem now comes up at least every other week and I can really understand the pain of our users. Find one example here [0].
>>
>> Is there really no chance this is ever going to change?
>>
>> Cheers,
>> Ollie
>>
>> [0] https://github.com/spring-projects/spring-hateoas/issues/688
>>
>>> Am 19.01.2017 um 08:40 schrieb Oliver Gierke <[hidden email]>:
>>>
>>> Hi Maurizio,
>>>
>>> thanks again for you in-depth explanation. I get all that and the design considerations behind the decisions that were made. I just wanted to point out that an ordinary user -- in general client code that uses APIs with SAM types -- *has to implicitly know* whether *any code* (potentially Java 6 based) downstream might attempt generics resolution before she can decide whether to use a Lambda with this SAM type.
>>>
>>> I think that's a very fundamental usability problem because it doesn't even need some arbitrary corner case. If there's a piece of code attempting the resolution anywhere downstream, it just won't work. Never. The only similar scenario (in terms of some parts of the reflection API not working as others) I remember is debug symbols on interface methods. That was changed in Java 8 with the -parameters flag. So I thought, it might be worth bringing this one up here as maybe the context has or will have changed for Java 9 / 10.
>>>
>>> Cheers,
>>> Ollie
>>>
>>>> Am 19.01.2017 um 02:52 schrieb Maurizio Cimadamore <[hidden email]>:
>>>>
>>>> Hi Oliver,
>>>> this request seems to be similar to other requests expressed in the past:
>>>>
>>>> Signature attribute on lambda method:
>>>>
>>>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009220.html
>>>>
>>>> Annotations on lambdas:
>>>>
>>>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-July/009662.html
>>>>
>>>>
>>>> The recurring theme is that we don't want to make promises on implementation details - that is how is a lambda implemented. For now, a lambda is implemented as a (dynamically generated) anonymous class instance - so certain requests seem to make sense. But as the implementation will get better at sharing, having to expose details such as signature attributes and such will become an obstacle to further improvements.
>>>>
>>>> For instance, the runtime does NOT have to distinguish between a Supplier<String> and a Supplier<Object> - example:
>>>>
>>>> class Test {
>>>>  void test() {
>>>>     Supplier<String> ss = this::m;
>>>>     Supplier<Object> si = this::m;
>>>>  }
>>>>
>>>>  String m() { return ""; }
>>>> }
>>>>
>>>> This gives the following bytceode:
>>>>
>>>> void test();
>>>>   descriptor: ()V
>>>>   flags:
>>>>   Code:
>>>>     stack=1, locals=3, args_size=1
>>>>        0: aload_0
>>>>        1: invokedynamic #2,  0              // InvokeDynamic #0:get:(LTest;)Ljava/util/function/Supplier;
>>>>        6: astore_1
>>>>        7: aload_0
>>>>        8: invokedynamic #3,  0              // InvokeDynamic #1:get:(LTest;)Ljava/util/function/Supplier;
>>>>       13: astore_2
>>>>       14: return
>>>>
>>>> BootstrapMethods:
>>>> 0: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>>>>   Method arguments:
>>>>     #19 ()Ljava/lang/Object;
>>>>     #20 invokevirtual Test.m:()Ljava/lang/String;
>>>>     #21 ()Ljava/lang/String;
>>>> 1: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>>>>   Method arguments:
>>>>     #19 ()Ljava/lang/Object;
>>>>     #20 invokevirtual Test.m:()Ljava/lang/String;
>>>>     #19 ()Ljava/lang/Object;
>>>>
>>>>
>>>> As you can see, both invokedynamic share the same SAM type - the erased java/util/function/Supplier. The only difference between the two indys is the method type passed as 3rd static argument - in one case ()String is passed, in the other ()Object is passed - that dictates the signature of the dynamically generated method implementing the sam type.
>>>>
>>>> In other words, if the lambda metafactory used the same instance for both method references, everything would still work (in fact, the generated bytecode is the same for both classes!).
>>>>
>>>> Maurizio
>>>>
>>>>
>>>>
>>>> On 18/01/17 18:06, Oliver Gierke wrote:
>>>>> Hi again,
>>>>>
>>>>> didn't get much response but it looks like there are some improvements for lambdas planned for at least JDK10 [0]. Is what I asked for below maybe a candidate for inclusion into those efforts?
>>>>>
>>>>> Is there maybe a better place to ask for this?
>>>>>
>>>>> Cheers,
>>>>> Ollie
>>>>>
>>>>> [0] https://www.infoq.com/news/2017/01/java10-lambda-leftovers
>>>>>
>>>>>> Am 23.12.2016 um 12:22 schrieb Oliver Gierke <[hidden email]>:
>>>>>>
>>>>>> Hi all,
>>>>>>
>>>>>> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise as some high-level API taking a SAM type as parameter is usually an indicator to users, that they can be used with Lambdas. If the object passed in is then inspected for generic types somewhere down the call stack this causes issues. Handing in a dedicated implementation of the SAM type is a workaround bit I think that's highly confusing and can be a source of errors hard to understand and debug.
>>>>>>
>>>>>> I've added an example below.
>>>>>>
>>>>>> Cheers,
>>>>>> Ollie
>>>>>>
>>>>>> public class LambdaTypeDetectionSample {
>>>>>>
>>>>>> public static void main(String[] args) {
>>>>>>
>>>>>> Function<Integer, String> lambdaFunction = i -> i.toString();
>>>>>> Function<Integer, String> oldschoolFunction = new Function<Integer, String>() {
>>>>>>
>>>>>> public String apply(Integer t) {
>>>>>> return t.toString();
>>>>>> }
>>>>>> };
>>>>>>
>>>>>> printTypeArguments(oldschoolFunction);
>>>>>>
>>>>>> // Yields:
>>>>>> // java.util.function.Function<java.lang.Integer, java.lang.String> is a class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
>>>>>> // class java.lang.Integer
>>>>>> // class java.lang.String
>>>>>>
>>>>>> printTypeArguments(lambdaFunction);
>>>>>>
>>>>>> // Yields:
>>>>>> // interface java.util.function.Function is a class java.lang.Class
>>>>>> }
>>>>>>
>>>>>> private static void printTypeArguments(Function<?, ?> function) {
>>>>>>
>>>>>> Type type = function.getClass().getGenericInterfaces()[0];
>>>>>>
>>>>>> System.out.println(type + " is a " + type.getClass());
>>>>>>
>>>>>> if (type instanceof ParameterizedType) {
>>>>>>
>>>>>> ParameterizedType functionInterface = (ParameterizedType) type;
>>>>>> Arrays.stream(functionInterface.getActualTypeArguments()).forEach(System.out::println);
>>>>>> }
>>>>>> }
>>>>>> }
>


signature.asc (849 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

Maurizio Cimadamore



On 11/01/18 11:38, Oliver Gierke wrote:
The problem with that approach is that whether Lambda usage is working does not depend on the declared interface but whether other code - potentially living somewhere completely else - is attempting generics resolution. I.e. the interface designer might not know up front.

In cases where the interface provider also is in charge of the processing code, the special annotation would certainly help.

I am planning to add a bit of defensive code to the processing side of things to reject runtime instances that lack the required generics information. Is there a canonical resource besides this mailing list thread I could refer to to explain why we need to reject those instances (a section in the spec or the like)?
I'm not aware of anything specific, but the spec certainly does a lot of work to deliberately _avoid_ saying what the runtime artifact associated with a lambda expression actually is. See:

https://docs.oracle.com/javase/specs/jls/se9/html/jls-15.html#jls-15.27.4

And more specifically, the bullets:

"
  • A new object need not be allocated on every evaluation.

  • Objects produced by different lambda expressions need not belong to different classes (if the bodies are identical, for example).

  • Every object produced by evaluation need not belong to the same class (captured local variables might be inlined, for example).

  • If an "existing instance" is available, it need not have been created at a previous lambda evaluation (it might have been allocated during the enclosing class's initialization, for example).

"


I believe esp. the second bullet goes against the kind of meta-info you would like to be associated with lambda expressions.

Maurizio



Cheers,
Ollie

Am 11.01.2018 um 12:18 schrieb Maurizio Cimadamore [hidden email]:

I think I still stand by the answers provided.

Brian's comment here is particularly insightful:

http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009268.html

E.g. it's not much that we're saying "works as designed" - it's more that we see this as some kind of short-term relief that is unavoidably cause much worse problems later down the road.

So, I see two options: we add all the required attributes/annotations, and commit to never change the implementation strategy again (ouch!); or we leave freedom for the implementation to come up with more efficient runtime strategies - which unfortunately works against the use case you are presenting.

That said, I agree with what's said in the evaluation you point at - the fact that many tools out there (IDEs) suggest you to turn all anon inner classes into lambdas might lead to people doing the refactoring w/o really understanding the implications for the framework they're working on.

I wonder whether some other resolution - e.g. an explicit mechanism to opt out of the functional interface business would help? E.g. if a functional interface is marked with @NoFunctionalInterface (provisional name of course), then the compiler (and the IDE) should refuse to consider this interface a suitable target type for a lambda expression/method reference. Would that mitigate the pain?

Maurizio

On 11/01/18 09:10, Oliver Gierke wrote:
Hi all,

I hate to bring this up again but as the adoption of Java 8 is growing significantly, this problem now comes up at least every other week and I can really understand the pain of our users. Find one example here [0].

Is there really no chance this is ever going to change?

Cheers,
Ollie

[0] https://github.com/spring-projects/spring-hateoas/issues/688

Am 19.01.2017 um 08:40 schrieb Oliver Gierke [hidden email]:

Hi Maurizio,

thanks again for you in-depth explanation. I get all that and the design considerations behind the decisions that were made. I just wanted to point out that an ordinary user -- in general client code that uses APIs with SAM types -- *has to implicitly know* whether *any code* (potentially Java 6 based) downstream might attempt generics resolution before she can decide whether to use a Lambda with this SAM type.

I think that's a very fundamental usability problem because it doesn't even need some arbitrary corner case. If there's a piece of code attempting the resolution anywhere downstream, it just won't work. Never. The only similar scenario (in terms of some parts of the reflection API not working as others) I remember is debug symbols on interface methods. That was changed in Java 8 with the -parameters flag. So I thought, it might be worth bringing this one up here as maybe the context has or will have changed for Java 9 / 10.

Cheers,
Ollie

Am 19.01.2017 um 02:52 schrieb Maurizio Cimadamore [hidden email]:

Hi Oliver,
this request seems to be similar to other requests expressed in the past:

Signature attribute on lambda method:

http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009220.html

Annotations on lambdas:

http://mail.openjdk.java.net/pipermail/compiler-dev/2015-July/009662.html


The recurring theme is that we don't want to make promises on implementation details - that is how is a lambda implemented. For now, a lambda is implemented as a (dynamically generated) anonymous class instance - so certain requests seem to make sense. But as the implementation will get better at sharing, having to expose details such as signature attributes and such will become an obstacle to further improvements.

For instance, the runtime does NOT have to distinguish between a Supplier<String> and a Supplier<Object> - example:

class Test {
 void test() {
    Supplier<String> ss = this::m;
    Supplier<Object> si = this::m;
 }

 String m() { return ""; }
}

This gives the following bytceode:

void test();
  descriptor: ()V
  flags:
  Code:
    stack=1, locals=3, args_size=1
       0: aload_0
       1: invokedynamic #2,  0              // InvokeDynamic #0:get:(LTest;)Ljava/util/function/Supplier;
       6: astore_1
       7: aload_0
       8: invokedynamic #3,  0              // InvokeDynamic #1:get:(LTest;)Ljava/util/function/Supplier;
      13: astore_2
      14: return

BootstrapMethods:
0: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  Method arguments:
    #19 ()Ljava/lang/Object;
    #20 invokevirtual Test.m:()Ljava/lang/String;
    #21 ()Ljava/lang/String;
1: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  Method arguments:
    #19 ()Ljava/lang/Object;
    #20 invokevirtual Test.m:()Ljava/lang/String;
    #19 ()Ljava/lang/Object;


As you can see, both invokedynamic share the same SAM type - the erased java/util/function/Supplier. The only difference between the two indys is the method type passed as 3rd static argument - in one case ()String is passed, in the other ()Object is passed - that dictates the signature of the dynamically generated method implementing the sam type.

In other words, if the lambda metafactory used the same instance for both method references, everything would still work (in fact, the generated bytecode is the same for both classes!).

Maurizio



On 18/01/17 18:06, Oliver Gierke wrote:
Hi again,

didn't get much response but it looks like there are some improvements for lambdas planned for at least JDK10 [0]. Is what I asked for below maybe a candidate for inclusion into those efforts?

Is there maybe a better place to ask for this?

Cheers,
Ollie

[0] https://www.infoq.com/news/2017/01/java10-lambda-leftovers

Am 23.12.2016 um 12:22 schrieb Oliver Gierke [hidden email]:

Hi all,

Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise as some high-level API taking a SAM type as parameter is usually an indicator to users, that they can be used with Lambdas. If the object passed in is then inspected for generic types somewhere down the call stack this causes issues. Handing in a dedicated implementation of the SAM type is a workaround bit I think that's highly confusing and can be a source of errors hard to understand and debug.

I've added an example below.

Cheers,
Ollie

public class LambdaTypeDetectionSample {

	public static void main(String[] args) {

		Function<Integer, String> lambdaFunction = i -> i.toString();
		Function<Integer, String> oldschoolFunction = new Function<Integer, String>() {

			public String apply(Integer t) {
				return t.toString();
			}
		};

		printTypeArguments(oldschoolFunction);

		// Yields:
		// java.util.function.Function<java.lang.Integer, java.lang.String> is a class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
		// class java.lang.Integer
		// class java.lang.String

		printTypeArguments(lambdaFunction);

		// Yields:
		// interface java.util.function.Function is a class java.lang.Class
	}

	private static void printTypeArguments(Function<?, ?> function) {

		Type type = function.getClass().getGenericInterfaces()[0];

		System.out.println(type + " is a " + type.getClass());

		if (type instanceof ParameterizedType) {

			ParameterizedType functionInterface = (ParameterizedType) type;
			Arrays.stream(functionInterface.getActualTypeArguments()).forEach(System.out::println);
		}
	}
}

      

    

Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

Oliver Gierke-2
In reply to this post by Maurizio Cimadamore
I fear that's quite a mouthful of internals thrown into a user's face, especially as everyone has literally learned for so many years that "SAM type -> Lambda", but I guess it's the best reference I can make.

Thanks, Maurizio!

> Am 11.01.2018 um 12:18 schrieb Maurizio Cimadamore <[hidden email]>:
>
> I think I still stand by the answers provided.
>
> Brian's comment here is particularly insightful:
>
> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009268.html
>
> E.g. it's not much that we're saying "works as designed" - it's more that we see this as some kind of short-term relief that is unavoidably cause much worse problems later down the road.
>
> So, I see two options: we add all the required attributes/annotations, and commit to never change the implementation strategy again (ouch!); or we leave freedom for the implementation to come up with more efficient runtime strategies - which unfortunately works against the use case you are presenting.
>
> That said, I agree with what's said in the evaluation you point at - the fact that many tools out there (IDEs) suggest you to turn all anon inner classes into lambdas might lead to people doing the refactoring w/o really understanding the implications for the framework they're working on.
>
> I wonder whether some other resolution - e.g. an explicit mechanism to opt out of the functional interface business would help? E.g. if a functional interface is marked with @NoFunctionalInterface (provisional name of course), then the compiler (and the IDE) should refuse to consider this interface a suitable target type for a lambda expression/method reference. Would that mitigate the pain?
>
> Maurizio
>
> On 11/01/18 09:10, Oliver Gierke wrote:
>> Hi all,
>>
>> I hate to bring this up again but as the adoption of Java 8 is growing significantly, this problem now comes up at least every other week and I can really understand the pain of our users. Find one example here [0].
>>
>> Is there really no chance this is ever going to change?
>>
>> Cheers,
>> Ollie
>>
>> [0] https://github.com/spring-projects/spring-hateoas/issues/688
>>
>>> Am 19.01.2017 um 08:40 schrieb Oliver Gierke <[hidden email]>:
>>>
>>> Hi Maurizio,
>>>
>>> thanks again for you in-depth explanation. I get all that and the design considerations behind the decisions that were made. I just wanted to point out that an ordinary user -- in general client code that uses APIs with SAM types -- *has to implicitly know* whether *any code* (potentially Java 6 based) downstream might attempt generics resolution before she can decide whether to use a Lambda with this SAM type.
>>>
>>> I think that's a very fundamental usability problem because it doesn't even need some arbitrary corner case. If there's a piece of code attempting the resolution anywhere downstream, it just won't work. Never. The only similar scenario (in terms of some parts of the reflection API not working as others) I remember is debug symbols on interface methods. That was changed in Java 8 with the -parameters flag. So I thought, it might be worth bringing this one up here as maybe the context has or will have changed for Java 9 / 10.
>>>
>>> Cheers,
>>> Ollie
>>>
>>>> Am 19.01.2017 um 02:52 schrieb Maurizio Cimadamore <[hidden email]>:
>>>>
>>>> Hi Oliver,
>>>> this request seems to be similar to other requests expressed in the past:
>>>>
>>>> Signature attribute on lambda method:
>>>>
>>>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009220.html
>>>>
>>>> Annotations on lambdas:
>>>>
>>>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-July/009662.html
>>>>
>>>>
>>>> The recurring theme is that we don't want to make promises on implementation details - that is how is a lambda implemented. For now, a lambda is implemented as a (dynamically generated) anonymous class instance - so certain requests seem to make sense. But as the implementation will get better at sharing, having to expose details such as signature attributes and such will become an obstacle to further improvements.
>>>>
>>>> For instance, the runtime does NOT have to distinguish between a Supplier<String> and a Supplier<Object> - example:
>>>>
>>>> class Test {
>>>>  void test() {
>>>>     Supplier<String> ss = this::m;
>>>>     Supplier<Object> si = this::m;
>>>>  }
>>>>
>>>>  String m() { return ""; }
>>>> }
>>>>
>>>> This gives the following bytceode:
>>>>
>>>> void test();
>>>>   descriptor: ()V
>>>>   flags:
>>>>   Code:
>>>>     stack=1, locals=3, args_size=1
>>>>        0: aload_0
>>>>        1: invokedynamic #2,  0              // InvokeDynamic #0:get:(LTest;)Ljava/util/function/Supplier;
>>>>        6: astore_1
>>>>        7: aload_0
>>>>        8: invokedynamic #3,  0              // InvokeDynamic #1:get:(LTest;)Ljava/util/function/Supplier;
>>>>       13: astore_2
>>>>       14: return
>>>>
>>>> BootstrapMethods:
>>>> 0: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>>>>   Method arguments:
>>>>     #19 ()Ljava/lang/Object;
>>>>     #20 invokevirtual Test.m:()Ljava/lang/String;
>>>>     #21 ()Ljava/lang/String;
>>>> 1: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>>>>   Method arguments:
>>>>     #19 ()Ljava/lang/Object;
>>>>     #20 invokevirtual Test.m:()Ljava/lang/String;
>>>>     #19 ()Ljava/lang/Object;
>>>>
>>>>
>>>> As you can see, both invokedynamic share the same SAM type - the erased java/util/function/Supplier. The only difference between the two indys is the method type passed as 3rd static argument - in one case ()String is passed, in the other ()Object is passed - that dictates the signature of the dynamically generated method implementing the sam type.
>>>>
>>>> In other words, if the lambda metafactory used the same instance for both method references, everything would still work (in fact, the generated bytecode is the same for both classes!).
>>>>
>>>> Maurizio
>>>>
>>>>
>>>>
>>>> On 18/01/17 18:06, Oliver Gierke wrote:
>>>>> Hi again,
>>>>>
>>>>> didn't get much response but it looks like there are some improvements for lambdas planned for at least JDK10 [0]. Is what I asked for below maybe a candidate for inclusion into those efforts?
>>>>>
>>>>> Is there maybe a better place to ask for this?
>>>>>
>>>>> Cheers,
>>>>> Ollie
>>>>>
>>>>> [0] https://www.infoq.com/news/2017/01/java10-lambda-leftovers
>>>>>
>>>>>> Am 23.12.2016 um 12:22 schrieb Oliver Gierke <[hidden email]>:
>>>>>>
>>>>>> Hi all,
>>>>>>
>>>>>> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise as some high-level API taking a SAM type as parameter is usually an indicator to users, that they can be used with Lambdas. If the object passed in is then inspected for generic types somewhere down the call stack this causes issues. Handing in a dedicated implementation of the SAM type is a workaround bit I think that's highly confusing and can be a source of errors hard to understand and debug.
>>>>>>
>>>>>> I've added an example below.
>>>>>>
>>>>>> Cheers,
>>>>>> Ollie
>>>>>>
>>>>>> public class LambdaTypeDetectionSample {
>>>>>>
>>>>>> public static void main(String[] args) {
>>>>>>
>>>>>> Function<Integer, String> lambdaFunction = i -> i.toString();
>>>>>> Function<Integer, String> oldschoolFunction = new Function<Integer, String>() {
>>>>>>
>>>>>> public String apply(Integer t) {
>>>>>> return t.toString();
>>>>>> }
>>>>>> };
>>>>>>
>>>>>> printTypeArguments(oldschoolFunction);
>>>>>>
>>>>>> // Yields:
>>>>>> // java.util.function.Function<java.lang.Integer, java.lang.String> is a class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
>>>>>> // class java.lang.Integer
>>>>>> // class java.lang.String
>>>>>>
>>>>>> printTypeArguments(lambdaFunction);
>>>>>>
>>>>>> // Yields:
>>>>>> // interface java.util.function.Function is a class java.lang.Class
>>>>>> }
>>>>>>
>>>>>> private static void printTypeArguments(Function<?, ?> function) {
>>>>>>
>>>>>> Type type = function.getClass().getGenericInterfaces()[0];
>>>>>>
>>>>>> System.out.println(type + " is a " + type.getClass());
>>>>>>
>>>>>> if (type instanceof ParameterizedType) {
>>>>>>
>>>>>> ParameterizedType functionInterface = (ParameterizedType) type;
>>>>>> Arrays.stream(functionInterface.getActualTypeArguments()).forEach(System.out::println);
>>>>>> }
>>>>>> }
>>>>>> }
>


signature.asc (849 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

Oliver Gierke-2
While I am currently investigating how to actually detect a lambda defined instance: is there a more reliable way than checking the class name to contain "$Lambda$"?

Cheers,
Ollie

> Am 11.01.2018 um 12:48 schrieb Oliver Gierke <[hidden email]>:
>
> I fear that's quite a mouthful of internals thrown into a user's face, especially as everyone has literally learned for so many years that "SAM type -> Lambda", but I guess it's the best reference I can make.
>
> Thanks, Maurizio!
>
>> Am 11.01.2018 um 12:18 schrieb Maurizio Cimadamore <[hidden email]>:
>>
>> I think I still stand by the answers provided.
>>
>> Brian's comment here is particularly insightful:
>>
>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009268.html
>>
>> E.g. it's not much that we're saying "works as designed" - it's more that we see this as some kind of short-term relief that is unavoidably cause much worse problems later down the road.
>>
>> So, I see two options: we add all the required attributes/annotations, and commit to never change the implementation strategy again (ouch!); or we leave freedom for the implementation to come up with more efficient runtime strategies - which unfortunately works against the use case you are presenting.
>>
>> That said, I agree with what's said in the evaluation you point at - the fact that many tools out there (IDEs) suggest you to turn all anon inner classes into lambdas might lead to people doing the refactoring w/o really understanding the implications for the framework they're working on.
>>
>> I wonder whether some other resolution - e.g. an explicit mechanism to opt out of the functional interface business would help? E.g. if a functional interface is marked with @NoFunctionalInterface (provisional name of course), then the compiler (and the IDE) should refuse to consider this interface a suitable target type for a lambda expression/method reference. Would that mitigate the pain?
>>
>> Maurizio
>>
>> On 11/01/18 09:10, Oliver Gierke wrote:
>>> Hi all,
>>>
>>> I hate to bring this up again but as the adoption of Java 8 is growing significantly, this problem now comes up at least every other week and I can really understand the pain of our users. Find one example here [0].
>>>
>>> Is there really no chance this is ever going to change?
>>>
>>> Cheers,
>>> Ollie
>>>
>>> [0] https://github.com/spring-projects/spring-hateoas/issues/688
>>>
>>>> Am 19.01.2017 um 08:40 schrieb Oliver Gierke <[hidden email]>:
>>>>
>>>> Hi Maurizio,
>>>>
>>>> thanks again for you in-depth explanation. I get all that and the design considerations behind the decisions that were made. I just wanted to point out that an ordinary user -- in general client code that uses APIs with SAM types -- *has to implicitly know* whether *any code* (potentially Java 6 based) downstream might attempt generics resolution before she can decide whether to use a Lambda with this SAM type.
>>>>
>>>> I think that's a very fundamental usability problem because it doesn't even need some arbitrary corner case. If there's a piece of code attempting the resolution anywhere downstream, it just won't work. Never. The only similar scenario (in terms of some parts of the reflection API not working as others) I remember is debug symbols on interface methods. That was changed in Java 8 with the -parameters flag. So I thought, it might be worth bringing this one up here as maybe the context has or will have changed for Java 9 / 10.
>>>>
>>>> Cheers,
>>>> Ollie
>>>>
>>>>> Am 19.01.2017 um 02:52 schrieb Maurizio Cimadamore <[hidden email]>:
>>>>>
>>>>> Hi Oliver,
>>>>> this request seems to be similar to other requests expressed in the past:
>>>>>
>>>>> Signature attribute on lambda method:
>>>>>
>>>>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009220.html
>>>>>
>>>>> Annotations on lambdas:
>>>>>
>>>>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-July/009662.html
>>>>>
>>>>>
>>>>> The recurring theme is that we don't want to make promises on implementation details - that is how is a lambda implemented. For now, a lambda is implemented as a (dynamically generated) anonymous class instance - so certain requests seem to make sense. But as the implementation will get better at sharing, having to expose details such as signature attributes and such will become an obstacle to further improvements.
>>>>>
>>>>> For instance, the runtime does NOT have to distinguish between a Supplier<String> and a Supplier<Object> - example:
>>>>>
>>>>> class Test {
>>>>> void test() {
>>>>>    Supplier<String> ss = this::m;
>>>>>    Supplier<Object> si = this::m;
>>>>> }
>>>>>
>>>>> String m() { return ""; }
>>>>> }
>>>>>
>>>>> This gives the following bytceode:
>>>>>
>>>>> void test();
>>>>>  descriptor: ()V
>>>>>  flags:
>>>>>  Code:
>>>>>    stack=1, locals=3, args_size=1
>>>>>       0: aload_0
>>>>>       1: invokedynamic #2,  0              // InvokeDynamic #0:get:(LTest;)Ljava/util/function/Supplier;
>>>>>       6: astore_1
>>>>>       7: aload_0
>>>>>       8: invokedynamic #3,  0              // InvokeDynamic #1:get:(LTest;)Ljava/util/function/Supplier;
>>>>>      13: astore_2
>>>>>      14: return
>>>>>
>>>>> BootstrapMethods:
>>>>> 0: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>>>>>  Method arguments:
>>>>>    #19 ()Ljava/lang/Object;
>>>>>    #20 invokevirtual Test.m:()Ljava/lang/String;
>>>>>    #21 ()Ljava/lang/String;
>>>>> 1: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>>>>>  Method arguments:
>>>>>    #19 ()Ljava/lang/Object;
>>>>>    #20 invokevirtual Test.m:()Ljava/lang/String;
>>>>>    #19 ()Ljava/lang/Object;
>>>>>
>>>>>
>>>>> As you can see, both invokedynamic share the same SAM type - the erased java/util/function/Supplier. The only difference between the two indys is the method type passed as 3rd static argument - in one case ()String is passed, in the other ()Object is passed - that dictates the signature of the dynamically generated method implementing the sam type.
>>>>>
>>>>> In other words, if the lambda metafactory used the same instance for both method references, everything would still work (in fact, the generated bytecode is the same for both classes!).
>>>>>
>>>>> Maurizio
>>>>>
>>>>>
>>>>>
>>>>> On 18/01/17 18:06, Oliver Gierke wrote:
>>>>>> Hi again,
>>>>>>
>>>>>> didn't get much response but it looks like there are some improvements for lambdas planned for at least JDK10 [0]. Is what I asked for below maybe a candidate for inclusion into those efforts?
>>>>>>
>>>>>> Is there maybe a better place to ask for this?
>>>>>>
>>>>>> Cheers,
>>>>>> Ollie
>>>>>>
>>>>>> [0] https://www.infoq.com/news/2017/01/java10-lambda-leftovers
>>>>>>
>>>>>>> Am 23.12.2016 um 12:22 schrieb Oliver Gierke <[hidden email]>:
>>>>>>>
>>>>>>> Hi all,
>>>>>>>
>>>>>>> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise as some high-level API taking a SAM type as parameter is usually an indicator to users, that they can be used with Lambdas. If the object passed in is then inspected for generic types somewhere down the call stack this causes issues. Handing in a dedicated implementation of the SAM type is a workaround bit I think that's highly confusing and can be a source of errors hard to understand and debug.
>>>>>>>
>>>>>>> I've added an example below.
>>>>>>>
>>>>>>> Cheers,
>>>>>>> Ollie
>>>>>>>
>>>>>>> public class LambdaTypeDetectionSample {
>>>>>>>
>>>>>>> public static void main(String[] args) {
>>>>>>>
>>>>>>> Function<Integer, String> lambdaFunction = i -> i.toString();
>>>>>>> Function<Integer, String> oldschoolFunction = new Function<Integer, String>() {
>>>>>>>
>>>>>>> public String apply(Integer t) {
>>>>>>> return t.toString();
>>>>>>> }
>>>>>>> };
>>>>>>>
>>>>>>> printTypeArguments(oldschoolFunction);
>>>>>>>
>>>>>>> // Yields:
>>>>>>> // java.util.function.Function<java.lang.Integer, java.lang.String> is a class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
>>>>>>> // class java.lang.Integer
>>>>>>> // class java.lang.String
>>>>>>>
>>>>>>> printTypeArguments(lambdaFunction);
>>>>>>>
>>>>>>> // Yields:
>>>>>>> // interface java.util.function.Function is a class java.lang.Class
>>>>>>> }
>>>>>>>
>>>>>>> private static void printTypeArguments(Function<?, ?> function) {
>>>>>>>
>>>>>>> Type type = function.getClass().getGenericInterfaces()[0];
>>>>>>>
>>>>>>> System.out.println(type + " is a " + type.getClass());
>>>>>>>
>>>>>>> if (type instanceof ParameterizedType) {
>>>>>>>
>>>>>>> ParameterizedType functionInterface = (ParameterizedType) type;
>>>>>>> Arrays.stream(functionInterface.getActualTypeArguments()).forEach(System.out::println);
>>>>>>> }
>>>>>>> }
>>>>>>> }
>>
>


signature.asc (849 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

David Lloyd-2
In reply to this post by Oliver Gierke-2
On Thu, Jan 11, 2018 at 3:10 AM, Oliver Gierke <[hidden email]> wrote:
> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise...

Here's a non-lambda example that demonstrates that inspecting *any*
class for generic type parameters is fragile and not a good idea:

public final class Hasher<T> implements ToIntFunction<T> {
    int applyAsInt(T value) { return value.hashCode(); }
}

Oops, now all my concrete implementations have generic parameters.

Telling users they cannot have generic parameters on their
implementation classes is poor API design.
--
- DML
Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

Oliver Gierke-2
> Am 11.01.2018 um 14:56 schrieb David Lloyd <[hidden email]>:

>
> On Thu, Jan 11, 2018 at 3:10 AM, Oliver Gierke <[hidden email]> wrote:
>> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise...
>
> Here's a non-lambda example that demonstrates that inspecting *any*
> class for generic type parameters is fragile and not a good idea:
>
> public final class Hasher<T> implements ToIntFunction<T> {
>    int applyAsInt(T value) { return value.hashCode(); }
> }
>
> Oops, now all my concrete implementations have generic parameters.
I'm afraid I can't follow.

> Telling users they cannot have generic parameters on their
> implementation classes is poor API design.

Who is actually doing that?

Cheers,
Ollie


signature.asc (849 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

Maurizio Cimadamore
In reply to this post by Oliver Gierke-2
I think that's your best bet. The name should contain $$Lambda$ followed
by some number. Of course this is very impl specific, and it would be
possible for other runtimes to do different things (e.g. the name is not
specified anywhere).

Maurizio


On 11/01/18 13:49, Oliver Gierke wrote:

> While I am currently investigating how to actually detect a lambda defined instance: is there a more reliable way than checking the class name to contain "$Lambda$"?
>
> Cheers,
> Ollie
>
>> Am 11.01.2018 um 12:48 schrieb Oliver Gierke <[hidden email]>:
>>
>> I fear that's quite a mouthful of internals thrown into a user's face, especially as everyone has literally learned for so many years that "SAM type -> Lambda", but I guess it's the best reference I can make.
>>
>> Thanks, Maurizio!
>>
>>> Am 11.01.2018 um 12:18 schrieb Maurizio Cimadamore <[hidden email]>:
>>>
>>> I think I still stand by the answers provided.
>>>
>>> Brian's comment here is particularly insightful:
>>>
>>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009268.html
>>>
>>> E.g. it's not much that we're saying "works as designed" - it's more that we see this as some kind of short-term relief that is unavoidably cause much worse problems later down the road.
>>>
>>> So, I see two options: we add all the required attributes/annotations, and commit to never change the implementation strategy again (ouch!); or we leave freedom for the implementation to come up with more efficient runtime strategies - which unfortunately works against the use case you are presenting.
>>>
>>> That said, I agree with what's said in the evaluation you point at - the fact that many tools out there (IDEs) suggest you to turn all anon inner classes into lambdas might lead to people doing the refactoring w/o really understanding the implications for the framework they're working on.
>>>
>>> I wonder whether some other resolution - e.g. an explicit mechanism to opt out of the functional interface business would help? E.g. if a functional interface is marked with @NoFunctionalInterface (provisional name of course), then the compiler (and the IDE) should refuse to consider this interface a suitable target type for a lambda expression/method reference. Would that mitigate the pain?
>>>
>>> Maurizio
>>>
>>> On 11/01/18 09:10, Oliver Gierke wrote:
>>>> Hi all,
>>>>
>>>> I hate to bring this up again but as the adoption of Java 8 is growing significantly, this problem now comes up at least every other week and I can really understand the pain of our users. Find one example here [0].
>>>>
>>>> Is there really no chance this is ever going to change?
>>>>
>>>> Cheers,
>>>> Ollie
>>>>
>>>> [0] https://github.com/spring-projects/spring-hateoas/issues/688
>>>>
>>>>> Am 19.01.2017 um 08:40 schrieb Oliver Gierke <[hidden email]>:
>>>>>
>>>>> Hi Maurizio,
>>>>>
>>>>> thanks again for you in-depth explanation. I get all that and the design considerations behind the decisions that were made. I just wanted to point out that an ordinary user -- in general client code that uses APIs with SAM types -- *has to implicitly know* whether *any code* (potentially Java 6 based) downstream might attempt generics resolution before she can decide whether to use a Lambda with this SAM type.
>>>>>
>>>>> I think that's a very fundamental usability problem because it doesn't even need some arbitrary corner case. If there's a piece of code attempting the resolution anywhere downstream, it just won't work. Never. The only similar scenario (in terms of some parts of the reflection API not working as others) I remember is debug symbols on interface methods. That was changed in Java 8 with the -parameters flag. So I thought, it might be worth bringing this one up here as maybe the context has or will have changed for Java 9 / 10.
>>>>>
>>>>> Cheers,
>>>>> Ollie
>>>>>
>>>>>> Am 19.01.2017 um 02:52 schrieb Maurizio Cimadamore <[hidden email]>:
>>>>>>
>>>>>> Hi Oliver,
>>>>>> this request seems to be similar to other requests expressed in the past:
>>>>>>
>>>>>> Signature attribute on lambda method:
>>>>>>
>>>>>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009220.html
>>>>>>
>>>>>> Annotations on lambdas:
>>>>>>
>>>>>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-July/009662.html
>>>>>>
>>>>>>
>>>>>> The recurring theme is that we don't want to make promises on implementation details - that is how is a lambda implemented. For now, a lambda is implemented as a (dynamically generated) anonymous class instance - so certain requests seem to make sense. But as the implementation will get better at sharing, having to expose details such as signature attributes and such will become an obstacle to further improvements.
>>>>>>
>>>>>> For instance, the runtime does NOT have to distinguish between a Supplier<String> and a Supplier<Object> - example:
>>>>>>
>>>>>> class Test {
>>>>>> void test() {
>>>>>>     Supplier<String> ss = this::m;
>>>>>>     Supplier<Object> si = this::m;
>>>>>> }
>>>>>>
>>>>>> String m() { return ""; }
>>>>>> }
>>>>>>
>>>>>> This gives the following bytceode:
>>>>>>
>>>>>> void test();
>>>>>>   descriptor: ()V
>>>>>>   flags:
>>>>>>   Code:
>>>>>>     stack=1, locals=3, args_size=1
>>>>>>        0: aload_0
>>>>>>        1: invokedynamic #2,  0              // InvokeDynamic #0:get:(LTest;)Ljava/util/function/Supplier;
>>>>>>        6: astore_1
>>>>>>        7: aload_0
>>>>>>        8: invokedynamic #3,  0              // InvokeDynamic #1:get:(LTest;)Ljava/util/function/Supplier;
>>>>>>       13: astore_2
>>>>>>       14: return
>>>>>>
>>>>>> BootstrapMethods:
>>>>>> 0: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>>>>>>   Method arguments:
>>>>>>     #19 ()Ljava/lang/Object;
>>>>>>     #20 invokevirtual Test.m:()Ljava/lang/String;
>>>>>>     #21 ()Ljava/lang/String;
>>>>>> 1: #18 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
>>>>>>   Method arguments:
>>>>>>     #19 ()Ljava/lang/Object;
>>>>>>     #20 invokevirtual Test.m:()Ljava/lang/String;
>>>>>>     #19 ()Ljava/lang/Object;
>>>>>>
>>>>>>
>>>>>> As you can see, both invokedynamic share the same SAM type - the erased java/util/function/Supplier. The only difference between the two indys is the method type passed as 3rd static argument - in one case ()String is passed, in the other ()Object is passed - that dictates the signature of the dynamically generated method implementing the sam type.
>>>>>>
>>>>>> In other words, if the lambda metafactory used the same instance for both method references, everything would still work (in fact, the generated bytecode is the same for both classes!).
>>>>>>
>>>>>> Maurizio
>>>>>>
>>>>>>
>>>>>>
>>>>>> On 18/01/17 18:06, Oliver Gierke wrote:
>>>>>>> Hi again,
>>>>>>>
>>>>>>> didn't get much response but it looks like there are some improvements for lambdas planned for at least JDK10 [0]. Is what I asked for below maybe a candidate for inclusion into those efforts?
>>>>>>>
>>>>>>> Is there maybe a better place to ask for this?
>>>>>>>
>>>>>>> Cheers,
>>>>>>> Ollie
>>>>>>>
>>>>>>> [0] https://www.infoq.com/news/2017/01/java10-lambda-leftovers
>>>>>>>
>>>>>>>> Am 23.12.2016 um 12:22 schrieb Oliver Gierke <[hidden email]>:
>>>>>>>>
>>>>>>>> Hi all,
>>>>>>>>
>>>>>>>> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise as some high-level API taking a SAM type as parameter is usually an indicator to users, that they can be used with Lambdas. If the object passed in is then inspected for generic types somewhere down the call stack this causes issues. Handing in a dedicated implementation of the SAM type is a workaround bit I think that's highly confusing and can be a source of errors hard to understand and debug.
>>>>>>>>
>>>>>>>> I've added an example below.
>>>>>>>>
>>>>>>>> Cheers,
>>>>>>>> Ollie
>>>>>>>>
>>>>>>>> public class LambdaTypeDetectionSample {
>>>>>>>>
>>>>>>>> public static void main(String[] args) {
>>>>>>>>
>>>>>>>> Function<Integer, String> lambdaFunction = i -> i.toString();
>>>>>>>> Function<Integer, String> oldschoolFunction = new Function<Integer, String>() {
>>>>>>>>
>>>>>>>> public String apply(Integer t) {
>>>>>>>> return t.toString();
>>>>>>>> }
>>>>>>>> };
>>>>>>>>
>>>>>>>> printTypeArguments(oldschoolFunction);
>>>>>>>>
>>>>>>>> // Yields:
>>>>>>>> // java.util.function.Function<java.lang.Integer, java.lang.String> is a class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
>>>>>>>> // class java.lang.Integer
>>>>>>>> // class java.lang.String
>>>>>>>>
>>>>>>>> printTypeArguments(lambdaFunction);
>>>>>>>>
>>>>>>>> // Yields:
>>>>>>>> // interface java.util.function.Function is a class java.lang.Class
>>>>>>>> }
>>>>>>>>
>>>>>>>> private static void printTypeArguments(Function<?, ?> function) {
>>>>>>>>
>>>>>>>> Type type = function.getClass().getGenericInterfaces()[0];
>>>>>>>>
>>>>>>>> System.out.println(type + " is a " + type.getClass());
>>>>>>>>
>>>>>>>> if (type instanceof ParameterizedType) {
>>>>>>>>
>>>>>>>> ParameterizedType functionInterface = (ParameterizedType) type;
>>>>>>>> Arrays.stream(functionInterface.getActualTypeArguments()).forEach(System.out::println);
>>>>>>>> }
>>>>>>>> }
>>>>>>>> }

Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

David Lloyd-2
In reply to this post by Oliver Gierke-2
On Thu, Jan 11, 2018 at 8:03 AM, Oliver Gierke <[hidden email]> wrote:

>> Am 11.01.2018 um 14:56 schrieb David Lloyd <[hidden email]>:
>>
>> On Thu, Jan 11, 2018 at 3:10 AM, Oliver Gierke <[hidden email]> wrote:
>>> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise...
>>
>> Here's a non-lambda example that demonstrates that inspecting *any*
>> class for generic type parameters is fragile and not a good idea:
>>
>> public final class Hasher<T> implements ToIntFunction<T> {
>>    int applyAsInt(T value) { return value.hashCode(); }
>> }
>>
>> Oops, now all my concrete implementations have generic parameters.
>
> I'm afraid I can't follow.

Your example code which prints the actual type arguments will fail
because there is no actual type argument declared on the generic
*type*, only the *instance*.  If you create an API which relies on
getting actual types off of an object instance's class, you've made a
usability mistake.

>> Telling users they cannot have generic parameters on their
>> implementation classes is poor API design.
>
> Who is actually doing that?

"I just wanted to point out that an ordinary user -- in general client
code that uses APIs with SAM types -- *has to implicitly know* whether
*any code* (potentially Java 6 based) downstream might attempt
generics resolution before she can decide whether to use a Lambda with
this SAM type."

Anyone who wrote one of these APIs is actually doing that.  It has
nothing to do with lambdas, and everything to do with APIs relying on
the assumption that implementation classes will have actual values for
every type parameter that it may inherit.  This assumption is flawed
because it's completely normal for a user to have a single function
implementation that works for many types; this is a solid
memory-conserving technique.  The only thing broken by this are
"clever" APIs that require usage of getActualTypeArguments() to get
necessary information from the user.

--
- DML
Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

Oliver Gierke-2
Again, I think we're discussing different things here. You refer to the fact that from an instance of a generic class, without creating an anonymous subtype, there's no way to find out about the generic type they've been bound to. That's fine and - I guess - common knowledge amongst Java developers.

I am looking a different case and thus a different problem: Lambdas have been advertised as a direct alternative to anonymous classes (see [0], esp. section 12), and that's the reason IDEs unconditionally recommend to convert the latter into the former. However, for a given generic interface, reflecting over the declared generics in the Lambda variant behaves fundamentally different compared to an anonymous implementation.

So I am pretty passionless on whether that's "trying to be clever" or "bad". I was merely pointing out an inconsistency.

Cheers,
Ollie

[0] http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html

> Am 11.01.2018 um 15:20 schrieb David Lloyd <[hidden email]>:
>
> On Thu, Jan 11, 2018 at 8:03 AM, Oliver Gierke <[hidden email]> wrote:
>>> Am 11.01.2018 um 14:56 schrieb David Lloyd <[hidden email]>:
>>>
>>> On Thu, Jan 11, 2018 at 3:10 AM, Oliver Gierke <[hidden email]> wrote:
>>>> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise...
>>>
>>> Here's a non-lambda example that demonstrates that inspecting *any*
>>> class for generic type parameters is fragile and not a good idea:
>>>
>>> public final class Hasher<T> implements ToIntFunction<T> {
>>>   int applyAsInt(T value) { return value.hashCode(); }
>>> }
>>>
>>> Oops, now all my concrete implementations have generic parameters.
>>
>> I'm afraid I can't follow.
>
> Your example code which prints the actual type arguments will fail
> because there is no actual type argument declared on the generic
> *type*, only the *instance*.  If you create an API which relies on
> getting actual types off of an object instance's class, you've made a
> usability mistake.
>
>>> Telling users they cannot have generic parameters on their
>>> implementation classes is poor API design.
>>
>> Who is actually doing that?
>
> "I just wanted to point out that an ordinary user -- in general client
> code that uses APIs with SAM types -- *has to implicitly know* whether
> *any code* (potentially Java 6 based) downstream might attempt
> generics resolution before she can decide whether to use a Lambda with
> this SAM type."
>
> Anyone who wrote one of these APIs is actually doing that.  It has
> nothing to do with lambdas, and everything to do with APIs relying on
> the assumption that implementation classes will have actual values for
> every type parameter that it may inherit.  This assumption is flawed
> because it's completely normal for a user to have a single function
> implementation that works for many types; this is a solid
> memory-conserving technique.  The only thing broken by this are
> "clever" APIs that require usage of getActualTypeArguments() to get
> necessary information from the user.
>
> --
> - DML


signature.asc (849 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

eliasvasylenko
I don't think you are discussing different things at all, Oliver.

You have presented your argument from two points of view:

- Users can't pass lambdas to methods unless they know for sure that the implementation isn't relying on being able to reflect over generic type parameters.

- As a result, library implementors / API designers can't rely on generic type information being available.

David's point is that both of these statements were already true for regular classes before lambdas came along.

What difference does it make if you have one more special case you need your users to avoid? Either you already properly document these awkward restrictions of argument types where it is not obvious and deem it an acceptable risk, in which case you only need to make a minor change to this documentation, or your API was already broken.

On Thu, 11 Jan 2018 at 15:34 Oliver Gierke <[hidden email]> wrote:
Again, I think we're discussing different things here. You refer to the fact that from an instance of a generic class, without creating an anonymous subtype, there's no way to find out about the generic type they've been bound to. That's fine and - I guess - common knowledge amongst Java developers.

I am looking a different case and thus a different problem: Lambdas have been advertised as a direct alternative to anonymous classes (see [0], esp. section 12), and that's the reason IDEs unconditionally recommend to convert the latter into the former. However, for a given generic interface, reflecting over the declared generics in the Lambda variant behaves fundamentally different compared to an anonymous implementation.

So I am pretty passionless on whether that's "trying to be clever" or "bad". I was merely pointing out an inconsistency.

Cheers,
Ollie

[0] http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html

> Am 11.01.2018 um 15:20 schrieb David Lloyd <[hidden email]>:
>
> On Thu, Jan 11, 2018 at 8:03 AM, Oliver Gierke <[hidden email]> wrote:
>>> Am 11.01.2018 um 14:56 schrieb David Lloyd <[hidden email]>:
>>>
>>> On Thu, Jan 11, 2018 at 3:10 AM, Oliver Gierke <[hidden email]> wrote:
>>>> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise...
>>>
>>> Here's a non-lambda example that demonstrates that inspecting *any*
>>> class for generic type parameters is fragile and not a good idea:
>>>
>>> public final class Hasher<T> implements ToIntFunction<T> {
>>>   int applyAsInt(T value) { return value.hashCode(); }
>>> }
>>>
>>> Oops, now all my concrete implementations have generic parameters.
>>
>> I'm afraid I can't follow.
>
> Your example code which prints the actual type arguments will fail
> because there is no actual type argument declared on the generic
> *type*, only the *instance*.  If you create an API which relies on
> getting actual types off of an object instance's class, you've made a
> usability mistake.
>
>>> Telling users they cannot have generic parameters on their
>>> implementation classes is poor API design.
>>
>> Who is actually doing that?
>
> "I just wanted to point out that an ordinary user -- in general client
> code that uses APIs with SAM types -- *has to implicitly know* whether
> *any code* (potentially Java 6 based) downstream might attempt
> generics resolution before she can decide whether to use a Lambda with
> this SAM type."
>
> Anyone who wrote one of these APIs is actually doing that.  It has
> nothing to do with lambdas, and everything to do with APIs relying on
> the assumption that implementation classes will have actual values for
> every type parameter that it may inherit.  This assumption is flawed
> because it's completely normal for a user to have a single function
> implementation that works for many types; this is a solid
> memory-conserving technique.  The only thing broken by this are
> "clever" APIs that require usage of getActualTypeArguments() to get
> necessary information from the user.
>
> --
> - DML

Reply | Threaded
Open this post in threaded view
|

Re: Issues with generic type detection of SAM types implemented using lambdas

Oliver Gierke-2
Yes, and no. You basically argue we're discussing the same things, immediately describing two *different* things being discussed:

1. There's a difference between instances of a generic type and anonymous (or plain implementation) classes and that has always been the case. Essentially "new ArrayList<String>();" VS "new ArrayList<String>() {}" (mind the curly braces).

Yes. That's correct. Nobody is disputing that. We can assume Java developers being familiar with that distinction. There's no one arguing the two are exchangeable in terms of generics resolution.

2. There's a difference between Lambdas and anonymous classes. The former are widely presented as equivalent of the latter

*That* is my point. As I see every other week, in contrast to the one above, not a lot of Java developers are familiar with *that* difference and caught by surprise.

Function<Integer, String> lambdaFunction = i -> i.toString();
Function<Integer, String> oldschoolFunction = new Function<Integer, String>() {

  public String apply(Integer t) {
    return t.toString();
  }
};

Basically, 2 is invalidating 1 *if* we assume that the expressions in 2 are equivalent and virtually *every* resource around lambdas on the web suggest that they are. The upper right side (i -> …) is supposed to be a direct replace for the lower (new Function<…) and thus both variants fall onto the right side of my example in 1, when in fact they do not.

"The lower is creating type information to allow generics resolution" -> "The upper is equivalent to the lower" -> "The upper is creating type information to allow generics resolution". You might think that's crazily naive to assume that as you're all familiar with the complex intrinsics of all of that but it seems to be a very common source of confusion.

Anyway, I guess I've made my point an we're just gonna have to agree to disagree here :).

Cheers,
Ollie

> Am 11.01.2018 um 17:08 schrieb elias vasylenko <[hidden email]>:
>
> I don't think you are discussing different things at all, Oliver.
>
> You have presented your argument from two points of view:
>
> - Users can't pass lambdas to methods unless they know for sure that the implementation isn't relying on being able to reflect over generic type parameters.
>
> - As a result, library implementors / API designers can't rely on generic type information being available.
>
> David's point is that both of these statements were already true for regular classes before lambdas came along.
>
> What difference does it make if you have one more special case you need your users to avoid? Either you already properly document these awkward restrictions of argument types where it is not obvious and deem it an acceptable risk, in which case you only need to make a minor change to this documentation, or your API was already broken.
>
> On Thu, 11 Jan 2018 at 15:34 Oliver Gierke <[hidden email]> wrote:
> Again, I think we're discussing different things here. You refer to the fact that from an instance of a generic class, without creating an anonymous subtype, there's no way to find out about the generic type they've been bound to. That's fine and - I guess - common knowledge amongst Java developers.
>
> I am looking a different case and thus a different problem: Lambdas have been advertised as a direct alternative to anonymous classes (see [0], esp. section 12), and that's the reason IDEs unconditionally recommend to convert the latter into the former. However, for a given generic interface, reflecting over the declared generics in the Lambda variant behaves fundamentally different compared to an anonymous implementation.
>
> So I am pretty passionless on whether that's "trying to be clever" or "bad". I was merely pointing out an inconsistency.
>
> Cheers,
> Ollie
>
> [0] http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html
>
> > Am 11.01.2018 um 15:20 schrieb David Lloyd <[hidden email]>:
> >
> > On Thu, Jan 11, 2018 at 8:03 AM, Oliver Gierke <[hidden email]> wrote:
> >>> Am 11.01.2018 um 14:56 schrieb David Lloyd <[hidden email]>:
> >>>
> >>> On Thu, Jan 11, 2018 at 3:10 AM, Oliver Gierke <[hidden email]> wrote:
> >>>> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise...
> >>>
> >>> Here's a non-lambda example that demonstrates that inspecting *any*
> >>> class for generic type parameters is fragile and not a good idea:
> >>>
> >>> public final class Hasher<T> implements ToIntFunction<T> {
> >>>   int applyAsInt(T value) { return value.hashCode(); }
> >>> }
> >>>
> >>> Oops, now all my concrete implementations have generic parameters.
> >>
> >> I'm afraid I can't follow.
> >
> > Your example code which prints the actual type arguments will fail
> > because there is no actual type argument declared on the generic
> > *type*, only the *instance*.  If you create an API which relies on
> > getting actual types off of an object instance's class, you've made a
> > usability mistake.
> >
> >>> Telling users they cannot have generic parameters on their
> >>> implementation classes is poor API design.
> >>
> >> Who is actually doing that?
> >
> > "I just wanted to point out that an ordinary user -- in general client
> > code that uses APIs with SAM types -- *has to implicitly know* whether
> > *any code* (potentially Java 6 based) downstream might attempt
> > generics resolution before she can decide whether to use a Lambda with
> > this SAM type."
> >
> > Anyone who wrote one of these APIs is actually doing that.  It has
> > nothing to do with lambdas, and everything to do with APIs relying on
> > the assumption that implementation classes will have actual values for
> > every type parameter that it may inherit.  This assumption is flawed
> > because it's completely normal for a user to have a single function
> > implementation that works for many types; this is a solid
> > memory-conserving technique.  The only thing broken by this are
> > "clever" APIs that require usage of getActualTypeArguments() to get
> > necessary information from the user.
> >
> > --
> > - DML
>


signature.asc (849 bytes) Download Attachment