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