Re: Constructing a java lambda expression?

Alan Ruttenberg <[email protected]> Thu, 25 Aug 2022 10:13:31 -0400
Newsgroups gmane.editors.j.devel
Message-ID <CAFKQJ8njL1m3PbrYm=YFiUVQkbcf7dsOYC0tiZ-YqaJ2KT_Ehg@mail.gmail.com>
--00000000000094e4f305e7116874
Content-Type: text/plain; charset="UTF-8"

Thanks.

(defun get-abstract-methods (classname)
  (map 'list #"getName"
       (remove-if-not
        (lambda(x)
          (let ((modifiers (#"getModifiers" x)))
            (and (#"isAbstract" 'reflect.Modifier modifiers)
                 (not (#"isStatic" 'reflect.Modifier modifiers)))))
        (#"getDeclaredMethods" (find-java-class classname)))))

(defun java-lambda-parameter-information (classname methodname position)
  (let* ((method (find methodname (#"getDeclaredMethods" (find-java-class
classname))
                       :key #"getName" :test 'equalp))
         (parameter-type (elt (#"getParameterTypes" method) position))
         (abstract-methods (get-abstract-methods parameter-type)))
    (assert (#"isInterface" parameter-type) ()
            "lambda parameter type ~a should be an interface but isn't"
            (#"getName" parameter-type))
    (assert (= (length abstract-methods) 1) ()
            "Parameter ~a type ~a has more than one abstract method:
~{~a~^, ~}"
            position (#"getName" parameter-type) abstract-methods)
    (list (#"getName" parameter-type) (car abstract-methods))))

(java-lambda-parameter-information 'Nitfsegmentsflowimpl
"forEachImagesegment" 0)
-> ("java.util.function.Consumer" "accept")

Cheers,
Alan


On Thu, Aug 25, 2022 at 6:35 AM Alessio Stalla <[email protected]>
wrote:

> Because *andThen *is a *default method *in the interface, i.e. it's not
> abstract:
> https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html
> So, effectively, the interface only has one abstract method, and that's
> the target of lambda conversion.
> Now I have no idea if and how ABCL deals with default methods. If it's
> still stuck on Java 5/6 compatibility then I suspect that it can't, at
> least not in its Java code.
>
> On Thu, 25 Aug 2022 at 06:37, Alan Ruttenberg <[email protected]>
> wrote:
>
>> So in order to implement this I need to know the interface to use for the
>> lambda? Presumably I can determine this with reflection.
>>
>> An example of one of the functions that takes the lambda is defined:
>>
>> public final NitfSegmentsFlow forEachImageSegment(final
>> Consumer<ImageSegment> consumer)
>>
>> But consumer has two methods: accept and andThen
>>
>> Eventually it looks like accept is called on the consumer. So in this
>> case it looks like I need to use jinterface-implementation and define the
>> accept method, no lambda necessary?
>>
>> What confuses me now is if a lambda is passed as the test code I'm
>> reading does:
>>
>> forEachImageSegment(imageSegment -> {do something})
>>
>> How does java know that the lambda is the implementation of accept rather
>> than andThen. Or more practically, how do I figure out which method to
>> implement without reading the source code?
>>
>> Thanks,
>> Alan
>>
>>
>> On Wed, Aug 24, 2022 at 4:14 AM Alessio Stalla <[email protected]>
>> wrote:
>>
>>> Function is a convenient interface for cases when a more specific
>>> interface does not exist. But any Java interface with a single abstract
>>> method can be the target of a lambda expression:
>>>
>>> new Thread(() -> { System.out.println("foo"); }).run();
>>>
>>> The Thread constructor takes a Runnable argument, not a Function.
>>>
>>> Lambda is just syntax sugar for interfaces with a single method, so you
>>> can reproduce them on ABCL with the jinterface thing + some macrology.
>>>
>>> On Wed, 24 Aug 2022 at 05:10, Vibhu Mohindra <[email protected]>
>>> wrote:
>>>
>>>> On 23/08/2022 05:27, Alan Ruttenberg wrote:
>>>> > There's a library I want to use that takes a lambda as an argument.
>>>> > Anyone know how to construct one in ABCL?
>>>>
>>>> I assume it's a Java library taking a Java Lambda that you want to call
>>>> from ABCL.
>>>>
>>>> A Java Lambda is really an instance of one of the classes in
>>>> java.util.function. Which one depends on how many parameters it has and
>>>> whether it returns a value. Let's assume your library wants a Java
>>>> Lambda that has one parameter and returns a value. That's a
>>>> java.util.function.Function. Say it looks like this:
>>>>
>>>> //Lib.java
>>>> public class Lib {
>>>>    public static void f(java.util.function.Function f) {
>>>>      System.out.println("You answered: " + f.apply(5));
>>>>    }
>>>> }
>>>>
>>>> such that it can be used from Java with a Java Lambda like this:
>>>>
>>>> Lib.f((Object x) -> (Integer)x * (Integer)x);
>>>> => You answered: 25
>>>>
>>>> You'd like to give it a Lisp Lambda from ABCL as follows:
>>>>
>>>> (jstatic "f" "Lib" #'(lambda (x) (* x x)))
>>>>
>>>> but that's not allowed because although Java can implicitly convert
>>>> from
>>>> a Java Lambda to a java.util.function.Function, it can't convert from a
>>>> Lisp Lambda to a java.util.function.Function.
>>>>
>>>> A Lisp Lambda is really an org.armedbear.lisp.Function. So one solution
>>>> is to adapt that.
>>>>
>>>> //Adaptor.java
>>>> import org.armedbear.lisp.*;
>>>> public class Adaptor implements java.util.function.Function {
>>>>    private org.armedbear.lisp.Function lispFn;
>>>>    public Adaptor(org.armedbear.lisp.Function lispFunction) {
>>>>      this.lispFn = lispFunction;
>>>>    }
>>>>    public Object apply(Object input) {
>>>>      return lispFn.execute(
>>>>        JavaObject.getInstance(input, true)).javaInstance();
>>>>    }
>>>> }
>>>>
>>>> and use it from ABCL like this:
>>>>
>>>> (jstatic "f" "Lib" (jnew "Adaptor" #'(lambda (x) (* x x))))
>>>> => You answered: 25
>>>> => NIL
>>>>
>>>> ----
>>>> Notes:
>>>>
>>>> I'm on Java 10, ABCL-1.4.0 (which is old).
>>>> I did this to build and run, starting with the jar and two java files
>>>> in
>>>> the current directory:
>>>> javac -classpath abcl-1.4.0.jar:. Adaptor.java Lib.java
>>>> java -classpath abcl-1.4.0.jar:. org.armedbear.lisp.Main
>>>>  > (jstatic "f" "Lib" (jnew "Adaptor" #'(lambda (x) (* x x))))
>>>>
>>>> You can probably create the Adaptor class from within ABCL if you don't
>>>> like that it's written in Java, but I don't remember how to. ABCL's
>>>> documentation might describe such bytecode generation somewhere.
>>>>
>>>> Pointers:
>>>> Java: java.util.function.*
>>>> ABCL: In org.armedbear.lisp, LispObject, JavaObject, Function
>>>>
>>>>
>>>> --
>>>> Vibhu
>>>>
>>>>

--00000000000094e4f305e7116874
Content-Type: text/html; charset="UTF-8"
Content-Transfer-Encoding: quoted-printable

<div dir=3D"ltr"><div><span style=3D"font-family:arial,sans-serif">Thanks.<=
/span></div><div><span style=3D"font-family:arial,sans-serif"><br></span></=
div>(defun get-abstract-methods (classname)<br>=C2=A0 (map &#39;list #&quot=
;getName&quot;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0(remove-if-not <br>=C2=A0 =C2=
=A0 =C2=A0 =C2=A0 (lambda(x) <br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (let ((=
modifiers (#&quot;getModifiers&quot; x)))<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =
=C2=A0 =C2=A0 (and (#&quot;isAbstract&quot; &#39;reflect.Modifier modifiers=
)<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(not (#&=
quot;isStatic&quot; &#39;reflect.Modifier modifiers)))))<br><div>=C2=A0 =C2=
=A0 =C2=A0 =C2=A0 (#&quot;getDeclaredMethods&quot; (find-java-class classna=
me)))))</div><div><br></div>(defun java-lambda-parameter-information (class=
name methodname position)<br>=C2=A0 (let* ((method (find methodname (#&quot=
;getDeclaredMethods&quot; (find-java-class classname))<br>=C2=A0 =C2=A0 =C2=
=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0:key #&qu=
ot;getName&quot; :test &#39;equalp))<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(=
parameter-type (elt (#&quot;getParameterTypes&quot; method) position))<br>=
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(abstract-methods (get-abstract-methods p=
arameter-type)))<br>=C2=A0 =C2=A0 (assert (#&quot;isInterface&quot; paramet=
er-type) ()<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 &quot;lambda param=
eter type ~a should be an interface but isn&#39;t&quot; <br>=C2=A0 =C2=A0 =
=C2=A0 =C2=A0 =C2=A0 =C2=A0 (#&quot;getName&quot; parameter-type))<br>=C2=
=A0 =C2=A0 (assert (=3D (length abstract-methods) 1) ()<br>=C2=A0 =C2=A0 =
=C2=A0 =C2=A0 =C2=A0 =C2=A0 &quot;Parameter ~a type ~a has more than one ab=
stract method: ~{~a~^, ~}&quot;<br>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=
=A0 position (#&quot;getName&quot; parameter-type) abstract-methods)<br>=C2=
=A0 =C2=A0 (list (#&quot;getName&quot; parameter-type) (car abstract-method=
s))))<div><span style=3D"font-family:monospace"><br></span></div><div><span=
 style=3D"font-family:arial,sans-serif">(java-lambda-parameter-information =
&#39;Nitfsegmentsflowimpl &quot;forEachImagesegment&quot; 0)<br>-&gt; (&quo=
t;java.util.function.Consumer&quot; &quot;accept&quot;)</span></div><div><b=
r></div><div><font size=3D"2">Cheers,</font></div><div><font size=3D"2">Ala=
n</font><br></div><div><div><span style=3D"font-family:monospace"><br></spa=
n></div></div></div><br><div class=3D"gmail_quote"><div dir=3D"ltr" class=
=3D"gmail_attr">On Thu, Aug 25, 2022 at 6:35 AM Alessio Stalla &lt;<a href=
=3D"mailto:[email protected]">[email protected]</a>&gt; wrote:<=
br></div><blockquote class=3D"gmail_quote" style=3D"margin:0px 0px 0px 0.8e=
x;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir=3D"ltr"=
><div>Because <i>andThen </i>is a <b>default method </b>in the interface, i=
.e. it&#39;s not abstract: <a href=3D"https://docs.oracle.com/javase/8/docs=
/api/java/util/function/Consumer.html" target=3D"_blank">https://docs.oracl=
e.com/javase/8/docs/api/java/util/function/Consumer.html</a></div><div>So, =
effectively, the interface only has one abstract method, and that&#39;s the=
 target of lambda conversion.</div><div>Now I have no idea if and how ABCL =
deals with default methods. If it&#39;s still stuck on Java 5/6 compatibili=
ty then I suspect that it can&#39;t, at least not in its Java code.<br></di=
v></div><br><div class=3D"gmail_quote"><div dir=3D"ltr" class=3D"gmail_attr=
">On Thu, 25 Aug 2022 at 06:37, Alan Ruttenberg &lt;<a href=3D"mailto:alanr=
[email protected]" target=3D"_blank">[email protected]</a>&gt; wro=
te:<br></div><blockquote class=3D"gmail_quote" style=3D"margin:0px 0px 0px =
0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir=3D"=
ltr"><div>So in order to implement this I need to know the interface to use=
 for the lambda? Presumably I can determine this with reflection.<br></div>=
<div><br></div><div>An example of one of the functions that takes the lambd=
a is defined:</div><div><br></div><div>public final NitfSegmentsFlow forEac=
hImageSegment(final Consumer&lt;ImageSegment&gt; consumer) <br></div><div><=
br></div><div>But consumer has two methods: accept and andThen <br></div><d=
iv><br></div><div>Eventually it looks like accept is called on the consumer=
. So in this case it looks like I need to use jinterface-implementation and=
 define the accept method, no lambda necessary?</div><div><br></div><div>Wh=
at confuses me now is if a lambda is passed as the test code I&#39;m readin=
g does:</div><div><br></div><div>forEachImageSegment(imageSegment -&gt; {do=
 something})</div><div><br></div><div>How does java know that the lambda is=
 the implementation of accept rather than andThen. Or more practically, how=
 do I figure out which method to implement without reading the source code?=
<br></div><div><br></div><div>Thanks,<br></div><div>Alan</div><div><br></di=
v></div><br><div class=3D"gmail_quote"><div dir=3D"ltr" class=3D"gmail_attr=
">On Wed, Aug 24, 2022 at 4:14 AM Alessio Stalla &lt;<a href=3D"mailto:ales=
[email protected]" target=3D"_blank">[email protected]</a>&gt; wrot=
e:<br></div><blockquote class=3D"gmail_quote" style=3D"margin:0px 0px 0px 0=
.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div dir=3D"l=
tr"><div>Function is a convenient interface for cases when a more specific =
interface does not exist. But any Java interface with a single abstract met=
hod can be the target of a lambda expression:</div><div><pre style=3D"backg=
round-color:rgb(255,255,255);color:rgb(0,0,0);font-family:&quot;JetBrains M=
ono&quot;,monospace"><span style=3D"color:rgb(0,0,128);font-weight:bold">ne=
w </span>Thread(() -&gt; { System.<span style=3D"color:rgb(102,14,122);font=
-weight:bold;font-style:italic">out</span>.println(<span style=3D"color:rgb=
(0,128,0);font-weight:bold">&quot;foo&quot;</span>); }).run();</pre></div><=
div>The Thread constructor takes a Runnable argument, not a Function.</div>=
<div><br></div><div>Lambda is just syntax sugar for interfaces with a singl=
e method, so you can reproduce them on ABCL with the jinterface thing + som=
e macrology.<br></div></div><br><div class=3D"gmail_quote"><div dir=3D"ltr"=
 class=3D"gmail_attr">On Wed, 24 Aug 2022 at 05:10, Vibhu Mohindra &lt;<a h=
ref=3D"mailto:[email protected]" target=3D"_blank">vibhu.mohindra@gm=
ail.com</a>&gt; wrote:<br></div><blockquote class=3D"gmail_quote" style=3D"=
margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-lef=
t:1ex">On 23/08/2022 05:27, Alan Ruttenberg wrote:<br>
&gt; There&#39;s a library I want to use that takes a lambda as an argument=
. <br>
&gt; Anyone know how to construct one in ABCL?<br>
<br>
I assume it&#39;s a Java library taking a Java Lambda that you want to call=
 <br>
from ABCL.<br>
<br>
A Java Lambda is really an instance of one of the classes in <br>
java.util.function. Which one depends on how many parameters it has and <br=
>
whether it returns a value. Let&#39;s assume your library wants a Java <br>
Lambda that has one parameter and returns a value. That&#39;s a <br>
java.util.function.Function. Say it looks like this:<br>
<br>
//Lib.java<br>
public class Lib {<br>
=C2=A0 =C2=A0public static void f(java.util.function.Function f) {<br>
=C2=A0 =C2=A0 =C2=A0System.out.println(&quot;You answered: &quot; + f.apply=
(5));<br>
=C2=A0 =C2=A0}<br>
}<br>
<br>
such that it can be used from Java with a Java Lambda like this:<br>
<br>
Lib.f((Object x) -&gt; (Integer)x * (Integer)x);<br>
=3D&gt; You answered: 25<br>
<br>
You&#39;d like to give it a Lisp Lambda from ABCL as follows:<br>
<br>
(jstatic &quot;f&quot; &quot;Lib&quot; #&#39;(lambda (x) (* x x)))<br>
<br>
but that&#39;s not allowed because although Java can implicitly convert fro=
m <br>
a Java Lambda to a java.util.function.Function, it can&#39;t convert from a=
 <br>
Lisp Lambda to a java.util.function.Function.<br>
<br>
A Lisp Lambda is really an org.armedbear.lisp.Function. So one solution <br=
>
is to adapt that.<br>
<br>
//Adaptor.java<br>
import org.armedbear.lisp.*;<br>
public class Adaptor implements java.util.function.Function {<br>
=C2=A0 =C2=A0private org.armedbear.lisp.Function lispFn;<br>
=C2=A0 =C2=A0public Adaptor(org.armedbear.lisp.Function lispFunction) {<br>
=C2=A0 =C2=A0 =C2=A0this.lispFn =3D lispFunction;<br>
=C2=A0 =C2=A0}<br>
=C2=A0 =C2=A0public Object apply(Object input) {<br>
=C2=A0 =C2=A0 =C2=A0return lispFn.execute(<br>
=C2=A0 =C2=A0 =C2=A0 =C2=A0JavaObject.getInstance(input, true)).javaInstanc=
e();<br>
=C2=A0 =C2=A0}<br>
}<br>
<br>
and use it from ABCL like this:<br>
<br>
(jstatic &quot;f&quot; &quot;Lib&quot; (jnew &quot;Adaptor&quot; #&#39;(lam=
bda (x) (* x x))))<br>
=3D&gt; You answered: 25<br>
=3D&gt; NIL<br>
<br>
----<br>
Notes:<br>
<br>
I&#39;m on Java 10, ABCL-1.4.0 (which is old).<br>
I did this to build and run, starting with the jar and two java files in <b=
r>
the current directory:<br>
javac -classpath abcl-1.4.0.jar:. Adaptor.java Lib.java<br>
java -classpath abcl-1.4.0.jar:. org.armedbear.lisp.Main<br>
=C2=A0&gt; (jstatic &quot;f&quot; &quot;Lib&quot; (jnew &quot;Adaptor&quot;=
 #&#39;(lambda (x) (* x x))))<br>
<br>
You can probably create the Adaptor class from within ABCL if you don&#39;t=
 <br>
like that it&#39;s written in Java, but I don&#39;t remember how to. ABCL&#=
39;s <br>
documentation might describe such bytecode generation somewhere.<br>
<br>
Pointers:<br>
Java: java.util.function.*<br>
ABCL: In org.armedbear.lisp, LispObject, JavaObject, Function<br>
<br>
<br>
-- <br>
Vibhu<br>
<br>
</blockquote></div>
</blockquote></div>
</blockquote></div>
</blockquote></div>

--00000000000094e4f305e7116874--