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 <alessiostalla@gmail.com> 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 <alanruttenberg@gmail.com> 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 <alessiostalla@gmail.com> 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 <vibhu.mohindra@gmail.com> 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