You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@groovy.apache.org by Daniel Sun <re...@hotmail.com> on 2018/01/30 00:14:25 UTC

About the callable native lambda

Hi all,

      I'm trying to implement the callable native lambda(e.g.
`Function<Integer, Integer> f = e -> e + 1; assert 3 == f(2);`), and there
are 2 ideas come to my mind:

1) Generate a proxy implementing the FunctionInterface(e.g. `Function`,
`Consumer`, etc.) and a `Callable` interface(maybe we should create a new
one), the proxy will delegate invocations of `call(Object... args)` method
of `Callable` to the method of the FunctionInterface. As you know, `f(2)` is
actually `f.call(2)`, so we need the method `call`.
2) Transform `f(2)` to `f.apply(2)`, we can get the target method name by
the type of FunctionInterface. To make it clear, let's have a look at two
example: `Function`'s method is `apply`, `Consumer`'s method is `accept`.

       I prefer the first way, but I want to get advice from you :-)

       In addition, have you any idea about the issue[1]? If you do not know
off the top of your head, I'll have to investigate when I have some spare
time. Any help is appreciated!

Cheers,
Daniel.Sun

[1]
https://github.com/apache/groovy/blob/native-lambda/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java#L136



--
Sent from: http://groovy.329449.n5.nabble.com/Groovy-Dev-f372993.html

Re: About the callable native lambda

Posted by Daniel Sun <re...@hotmail.com>.
Hi Jesper,

    Given `f` is of SAM type, `f(params...)` is actually
`f.call(params...)`, which I transform to `f.<sam-method>(params...)`
eventually[1].

    The reason why I do not choose `f.<sam-method>(params...)` at the
beginning is that I'm not sure whether we can get enough type information of
`f`, in addition, as you said, the better solution is a breaking change. It
needs discussion in mailing list, which will take a long time to finish...

Cheers,
Daniel.Sun

[1]
https://github.com/apache/groovy/blob/c24c0b7e6a67dcdf277207d4261cfa6f2b55031f/src/main/java/org/codehaus/groovy/classgen/asm/InvocationWriter.java#L515



--
Sent from: http://groovy.329449.n5.nabble.com/Groovy-Dev-f372993.html

Re: About the callable native lambda

Posted by Jesper Steen Møller <je...@selskabet.org>.
(Sorry, first version went out too soon, thanks to the silly, silly Touch Bar)

> On 31 Jan 2018, at 23.01, MG <mg...@arscreat.com> wrote:
> 
> Hi Jesper,
> 
> seen from a Groovy user perspective your proposal seems to make sense to me.
> (I would at the same time hope you do not dent Daniel Sun's enthusiasm too much, because as far as I can tell he is currently doing alot of the heavy lifting in this project :-) )
> 
Yes, he is - and the lambda work has come a long way - I'm testing some scenarios, so i hope to lift a little, too!

> How do you think what you propose fares with regards to "the principle of least surprise" ? Are there any cases where this could lead to hard to track bugs / unexpected behavior ? From the top of my hat, that would be my biggest concern...

I see static and dynamic as different concerns: For dynamic invocation, there could be some surprises, like example posted by 'avafanasiev' on GitHub:

class R implements Runnable { void run(){}}

def m(Runnable r1, R r2, r3) {
   r1()
   r2()
   r3()
}

m(new R(), new R(), new R())

Currently, in the 'native-lambda' branch, r1() succeeds, whereas the latter two fail to run/compile (depending on dynamic/static compilation), as 'avafanasiev' commented on. I do find that confusing: Dynamically, my opinion is that the three should work the same.

For static compilation, r1() and r2() should work IMHO, and r3() should be rejected. This shouldn't surprise anyone, I think.

Also, surprise-wise, consider:

class Q implements Runnable, Supplier<String> { void run(){}; String get() { "Q" } }

def n(Runnable q1, Supplier <String> q2, Q q2, q3) {
   q1()
   q2()
   q3()
   q4()
}

n(new Q(), new Q(), new Q(), new Q())

I'm thinking that all four should FAIL dynamically, but statically, q1 and q2 should work, using the appropriate interface.

> "...only as a fallback if obj.call  doesn't exist" seems like the safer choice in this regard. Default behavior could also be made overridable by a class annotation (then it would become the programmer's responsibility, to make sure least surprise is not violated).
> Without that the question to me is: Would choosing "fallback if obj.call  doesn't exist" weaken the elegance of the whole concept too much ?

I don't think it would weaken the elegance.
We hardly need annotations - we could simply use the presence of GroovyCallable to find out which objects would prefer to be call()'ed directly.

-Jesper



Re: About the callable native lambda

Posted by Jesper Steen Møller <je...@selskabet.org>.
Hi again 

> On 1 Feb 2018, at 13.48, Jochen Theodorou <bl...@gmx.org> wrote:
> 
> [...]
>> For static compilation, r1() and r2() should work IMHO, and r3() should be rejected. This shouldn't surprise anyone, I think.
> 
> you mean because the m has a return value and r3() is void? That is currently passing compilation and the result will be null. This covers with the dynamic version.
> 

No, I mean because r3 (statically) is a java.lang.Object, and Object has no call() method and is not a SAM:

> you surely did mean:
> 
>>> def n(Runnable q1, Predicate<String> q2, Q q2, q3) {
>>>     q1()
>>>     q2()
>>>     q3() >>      q4()
>>> }
>>> m(new Q(), new Q(), new Q(), new Q())
> 
> I expect this to work then too ;)


Yeah, the mail wasn't complete. I've fixed the example, re-sent the mail, and suggested (IMHO) non-surprising semantics.

-Jesper


Re: About the callable native lambda

Posted by Jochen Theodorou <bl...@gmx.org>.

Am 01.02.2018 um 12:50 schrieb Jesper Steen Møller:
[...]
> class R implements Runnable { void run(){}}
> 
> def m(Runnable r1, R r2, r3) {
>      r1()
>      r2()
>      r3()
> }
> 
> m(new R(), new R(), new R())
> 
> Currently, in the 'native-lambda' branch, r1() succeeds, whereas the latter two fail to run/compile (depending on dynamic/static compilation), as 'avafanasiev' commented on. I do find that confusing: Dynamically, my opinion is that the three should work the same.
> 
> For static compilation, r1() and r2() should work IMHO, and r3() should be rejected. This shouldn't surprise anyone, I think.

you mean because the m has a return value and r3() is void? That is 
currently passing compilation and the result will be null. This covers 
with the dynamic version.

> Also, surprise-wise,
> 
> class Q implements Runnable, Predicate<String> { void run(){}; boolean test(String s) { s } }
> 
> def n(Runnable q1, Predicate<String> q2, Q q2, q3) {
>      r1()
>      r2()
>      r3()
> }
> 
> m(new R(), new R(), new R())


you surely did mean:

>> def n(Runnable q1, Predicate<String> q2, Q q2, q3) {
>>      q1()
>>      q2()
>>      q3() >>      q4()
>> }
>> 
>> m(new Q(), new Q(), new Q(), new Q())

I expect this to work then too ;)


bye Jochen

Re: About the callable native lambda

Posted by Jesper Steen Møller <je...@selskabet.org>.
> On 31 Jan 2018, at 23.01, MG <mg...@arscreat.com> wrote:
> 
> Hi Jesper,
> 
> seen from a Groovy user perspective your proposal seems to make sense to me.
> (I would at the same time hope you do not dent Daniel Sun's enthusiasm too much, because as far as I can tell he is currently doing alot of the heavy lifting in this project :-) )
> 
Yes, he is - and the lambda work has come a long way - I'm testing some scenarios, so i hope to lift a little, too!

> How do you think what you propose fares with regards to "the principle of least surprise" ? Are there any cases where this could lead to hard to track bugs / unexpected behavior ? From the top of my hat, that would be my biggest concern...

I see static and dynamic as different concerns: For dynamic invocation, there could be some surprises, like example posted by 'avafanasiev' on GitHub:

class R implements Runnable { void run(){}}

def m(Runnable r1, R r2, r3) {
    r1()
    r2()
    r3()
}

m(new R(), new R(), new R())

Currently, in the 'native-lambda' branch, r1() succeeds, whereas the latter two fail to run/compile (depending on dynamic/static compilation), as 'avafanasiev' commented on. I do find that confusing: Dynamically, my opinion is that the three should work the same.

For static compilation, r1() and r2() should work IMHO, and r3() should be rejected. This shouldn't surprise anyone, I think.

Also, surprise-wise, 

class Q implements Runnable, Predicate<String> { void run(){}; boolean test(String s) { s } }

def n(Runnable q1, Predicate<String> q2, Q q2, q3) {
    r1()
    r2()
    r3()
}

m(new R(), new R(), new R())





> "...only as a fallback if obj.call  doesn't exist" seems like the safer choice in this regard. Default behavior could also be made overridable by a class annotation (then it would become the programmer's responsibility, to make sure least surprise is not violated).
> Without that the question to me is: Would choosing "fallback if obj.call  doesn't exist" weaken the elegance of the whole concept too much ?
> 
> mg
> 
> 
> On 31.01.2018 10:00, Jesper Steen Møller wrote:
>> Hi list
>> 
>> FYI: This turned into a discussion of  the feature itself, on the GitHub commit thread. Basically, I'm proposing changing what "obj(params...)" means:
>>  - Non-SAM types: obj(params...) becomes obj.call(params...)
>>  - SAM types: obj(params...) becomes obj.<sam-method>(params...) - perhaps only as a fallback if obj.call doesn't exist.
>> 
>> This should be completely independent of how the lambda object itself was created.
>> 
>> I realize this is a potentially breaking change, but isn't it also a nice one?
>> Thoughts?
>> 
>> -Jesper
>> 
>>> On 31 Jan 2018, at 03.16, Daniel Sun <re...@hotmail.com> wrote:
>>> 
>>> Hi Jesper,
>>> 
>>>     I think your suggestion is very nice and I've completed callable native
>>> lambda according to option 2 :-)
>>> 
>>>     Here is the related commit:
>>> https://github.com/apache/groovy/commit/c24c0b7e6a67dcdf277207d4261cfa6f2b55031f
>>> 
>>> Cheers,
>>> Daniel.Sun
>>> 
>>> 
>>> 
>>> --
>>> Sent from: http://groovy.329449.n5.nabble.com/Groovy-Dev-f372993.html
>> 
> 


Re: About the callable native lambda

Posted by MG <mg...@arscreat.com>.
Hi Jesper,

seen from a Groovy user perspective your proposal seems to make sense to me.
(I would at the same time hope you do not dent Daniel Sun's enthusiasm 
too much, because as far as I can tell he is currently doing alot of the 
heavy lifting in this project :-) )

How do you think what you propose fares with regards to "the principle 
of least surprise" ? Are there any cases where this could lead to hard 
to track bugs / unexpected behavior ? From the top of my hat, that would 
be my biggest concern...

"...only as a fallback if obj.call  doesn't exist" seems like the safer 
choice in this regard. Default behavior could also be made overridable 
by a class annotation (then it would become the programmer's 
responsibility, to make sure least surprise is not violated).
Without that the question to me is: Would choosing "fallback if 
obj.call  doesn't exist" weaken the elegance of the whole concept too much ?

mg


On 31.01.2018 10:00, Jesper Steen Møller wrote:
> Hi list
>
> FYI: This turned into a discussion of  the feature itself, on the GitHub commit thread. Basically, I'm proposing changing what "obj(params...)" means:
>   - Non-SAM types: obj(params...) becomes obj.call(params...)
>   - SAM types: obj(params...) becomes obj.<sam-method>(params...) - perhaps only as a fallback if obj.call doesn't exist.
>
> This should be completely independent of how the lambda object itself was created.
>
> I realize this is a potentially breaking change, but isn't it also a nice one?
> Thoughts?
>
> -Jesper
>
>> On 31 Jan 2018, at 03.16, Daniel Sun <re...@hotmail.com> wrote:
>>
>> Hi Jesper,
>>
>>      I think your suggestion is very nice and I've completed callable native
>> lambda according to option 2 :-)
>>
>>      Here is the related commit:
>> https://github.com/apache/groovy/commit/c24c0b7e6a67dcdf277207d4261cfa6f2b55031f
>>
>> Cheers,
>> Daniel.Sun
>>
>>
>>
>> --
>> Sent from: http://groovy.329449.n5.nabble.com/Groovy-Dev-f372993.html
>


Re: About the callable native lambda

Posted by Jesper Steen Møller <je...@selskabet.org>.
Hi list

FYI: This turned into a discussion of  the feature itself, on the GitHub commit thread. Basically, I'm proposing changing what "obj(params...)" means:
 - Non-SAM types: obj(params...) becomes obj.call(params...)
 - SAM types: obj(params...) becomes obj.<sam-method>(params...) - perhaps only as a fallback if obj.call doesn't exist.

This should be completely independent of how the lambda object itself was created.

I realize this is a potentially breaking change, but isn't it also a nice one?
Thoughts?

-Jesper

> On 31 Jan 2018, at 03.16, Daniel Sun <re...@hotmail.com> wrote:
> 
> Hi Jesper,
> 
>     I think your suggestion is very nice and I've completed callable native
> lambda according to option 2 :-)
> 
>     Here is the related commit:
> https://github.com/apache/groovy/commit/c24c0b7e6a67dcdf277207d4261cfa6f2b55031f
> 
> Cheers,
> Daniel.Sun
> 
> 
> 
> --
> Sent from: http://groovy.329449.n5.nabble.com/Groovy-Dev-f372993.html


Re: About the callable native lambda

Posted by Daniel Sun <re...@hotmail.com>.
Hi Jesper,

     I think your suggestion is very nice and I've completed callable native
lambda according to option 2 :-)

     Here is the related commit:
https://github.com/apache/groovy/commit/c24c0b7e6a67dcdf277207d4261cfa6f2b55031f

Cheers,
Daniel.Sun



--
Sent from: http://groovy.329449.n5.nabble.com/Groovy-Dev-f372993.html

Re: About the callable native lambda

Posted by Jesper Steen Møller <je...@selskabet.org>.
Hi Daniel

I very much recommend going with option 2: It could and should work for any functional interface, not just the ones implemented in Groovy.
Ideally, there would be no difference between a "Java lambda" and a "Groovy lambda"

-Jesper

> On 30 Jan 2018, at 01.14, Daniel Sun <re...@hotmail.com> wrote:
> 
> Hi all,
> 
>      I'm trying to implement the callable native lambda(e.g.
> `Function<Integer, Integer> f = e -> e + 1; assert 3 == f(2);`), and there
> are 2 ideas come to my mind:
> 
> 1) Generate a proxy implementing the FunctionInterface(e.g. `Function`,
> `Consumer`, etc.) and a `Callable` interface(maybe we should create a new
> one), the proxy will delegate invocations of `call(Object... args)` method
> of `Callable` to the method of the FunctionInterface. As you know, `f(2)` is
> actually `f.call(2)`, so we need the method `call`.
> 2) Transform `f(2)` to `f.apply(2)`, we can get the target method name by
> the type of FunctionInterface. To make it clear, let's have a look at two
> example: `Function`'s method is `apply`, `Consumer`'s method is `accept`.
> 
>       I prefer the first way, but I want to get advice from you :-)
> 
>       In addition, have you any idea about the issue[1]? If you do not know
> off the top of your head, I'll have to investigate when I have some spare
> time. Any help is appreciated!
> 
> Cheers,
> Daniel.Sun
> 
> [1]
> https://github.com/apache/groovy/blob/native-lambda/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java#L136
> 
> 
> 
> --
> Sent from: http://groovy.329449.n5.nabble.com/Groovy-Dev-f372993.html


Re: About the callable native lambda

Posted by Daniel Sun <re...@hotmail.com>.
Hi Rémi,

     Thanks for your detailed explanation!

> so (2) seems to be a better option. 
     Yeah, I've chosen the option 2 :-)

      Here is the PR: https://github.com/apache/groovy/pull/654

      Welcome to review it ;-)

Cheers,
Daniel.Sun



--
Sent from: http://groovy.329449.n5.nabble.com/Groovy-Dev-f372993.html

Re: About the callable native lambda

Posted by Remi Forax <fo...@univ-mlv.fr>.
You should try to reuse the LambdaMetaFactory instead of generating your own proxy,
- generating a proxy means you have a way to define a class in module that do not own, so you have to use lookup.defineClass (or worst unsafe.defineClass), so you need to generate an invokedynamic
- you can generate the proxy at compile time but in that case, you loose the fact that lambdas reduce of disk footprint.
- JDK lambdas use a lightweight classloader so their code is unloaded if the lambda is GCed, something which is hard to emulate with your own proxy,
- JDK lambdas consider captured values as really final fields trusted by the VM, again something hard to emulate.

so (2) seems to be a better option.

cheers,
Rémi

----- Mail original -----
> De: "Daniel Sun" <re...@hotmail.com>
> À: "dev" <de...@groovy.incubator.apache.org>
> Envoyé: Mardi 30 Janvier 2018 01:14:25
> Objet: About the callable native lambda

> Hi all,
> 
>      I'm trying to implement the callable native lambda(e.g.
> `Function<Integer, Integer> f = e -> e + 1; assert 3 == f(2);`), and there
> are 2 ideas come to my mind:
> 
> 1) Generate a proxy implementing the FunctionInterface(e.g. `Function`,
> `Consumer`, etc.) and a `Callable` interface(maybe we should create a new
> one), the proxy will delegate invocations of `call(Object... args)` method
> of `Callable` to the method of the FunctionInterface. As you know, `f(2)` is
> actually `f.call(2)`, so we need the method `call`.
> 2) Transform `f(2)` to `f.apply(2)`, we can get the target method name by
> the type of FunctionInterface. To make it clear, let's have a look at two
> example: `Function`'s method is `apply`, `Consumer`'s method is `accept`.
> 
>       I prefer the first way, but I want to get advice from you :-)
> 
>       In addition, have you any idea about the issue[1]? If you do not know
> off the top of your head, I'll have to investigate when I have some spare
> time. Any help is appreciated!
> 
> Cheers,
> Daniel.Sun
> 
> [1]
> https://github.com/apache/groovy/blob/native-lambda/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java#L136
> 
> 
> 
> --
> Sent from: http://groovy.329449.n5.nabble.com/Groovy-Dev-f372993.html

Re: About the callable native lambda

Posted by Daniel Sun <re...@hotmail.com>.
Hi Rémi,

    As we can see, operandStack.pop() is commented, so the code will not
take effect ;)

    Jochen told me some operands left in the stack, I should have pop them
by myself, currently framework pops them for me automatically... so I tried
operandStack.pop(), but I got AIOOBE because stack is empty...

Cheers,
Daniel.Sun



--
Sent from: http://groovy.329449.n5.nabble.com/Groovy-Dev-f372993.html

Re: About the callable native lambda

Posted by Remi Forax <fo...@univ-mlv.fr>.

----- Mail original -----
> De: "Daniel Sun" <re...@hotmail.com>
> À: "dev" <de...@groovy.incubator.apache.org>
> Envoyé: Mardi 30 Janvier 2018 01:14:25
> Objet: About the callable native lambda

> Hi all,
> 

[...]

>       In addition, have you any idea about the issue[1]? If you do not know
> off the top of your head, I'll have to investigate when I have some spare
> time. Any help is appreciated!

you have a call to operandStack.pop() at the end of the method writeLambda, is it what you want ?

> 
> Cheers,
> Daniel.Sun
> 

Rémi

> [1]
> https://github.com/apache/groovy/blob/native-lambda/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesLambdaWriter.java#L136
> 
> 
> 
> --
> Sent from: http://groovy.329449.n5.nabble.com/Groovy-Dev-f372993.html