Discussion:
using lambda to implement one method interface
Sonny To
2017-09-17 15:32:31 UTC
Permalink
follwing docs at https://www.gnu.org/software/kawa/Anonymous-classes.html

(let ((handler (android.os.Handler (android.os.Looper:getMainLooper)))
(runnable (lambda ()
(android.util.Log:i "scm" "run me"))))
(handler:post runnable)
)

that doesn't seem to work. Do I need to give type hints to be able to do this?

I'm getting this error:

#|.....44|# #|.....45|# #|.....46|# #|.....47|# /dev/stdin:46:17:
warning - type function is incompatible with required type
java.lang.Runnable
java.lang.ClassCastException: don't know how to coerce
gnu.expr.LambdaExp$Closure to java.lang.Runnable
at gnu.bytecode.ObjectType.coerceFromObject(ObjectType.java:180)
at gnu.kawa.functions.Convert.apply2(Convert.java:38)
at gnu.mapping.Procedure2.applyToObject(Procedure2.java:62)
at java.lang.reflect.Method.invoke(Native Method)
at gnu.mapping.CallContext$ReflectMethodHandle.invokeExact(CallContext.java:726)
at gnu.mapping.Procedure.applyToConsumerDefault(Procedure.java:75)
at java.lang.reflect.Method.invoke(Native Method)
at gnu.mapping.CallContext$ReflectMethodHandle.invokeExact(CallContext.java:726)
at gnu.mapping.CallContext.runUntilDone(CallContext.java:586)
at gnu.mapping.CallContext.getFromContext(CallContext.java:616)
at gnu.expr.Expression.eval(Expression.java:52)
at gnu.expr.ApplyExp.apply(ApplyExp.java:161)
at gnu.expr.LetExp.apply(LetExp.java:72)
at gnu.expr.ModuleExp.evalModule2(ModuleExp.java:281)
at gnu.expr.ModuleExp.evalModule(ModuleExp.java:211)
at kawa.Shell.run(Shell.java:283)
at kawa.Shell.run(Shell.java:196)
at kawa.Shell.run(Shell.java:183)
at kawa.TelnetRepl.apply0(TelnetRepl.java:25)
at gnu.mapping.RunnableClosure.run(RunnableClosure.java:75)
at java.lang.Thread.run(Thread.java:764)
Sonny To
2017-09-17 15:38:04 UTC
Permalink
this works

(let ((handler (android.os.Handler (android.os.Looper:getMainLooper)))
)
(handler:post (lambda ()
(android.util.Log:i "scm" "run me")))
)

this is non-intuitive. why doesn't it work if i bind the lambda to a
symbol? seems like a bug to me
Post by Sonny To
follwing docs at https://www.gnu.org/software/kawa/Anonymous-classes.html
(let ((handler (android.os.Handler (android.os.Looper:getMainLooper)))
(runnable (lambda ()
(android.util.Log:i "scm" "run me"))))
(handler:post runnable)
)
that doesn't seem to work. Do I need to give type hints to be able to do this?
warning - type function is incompatible with required type
java.lang.Runnable
java.lang.ClassCastException: don't know how to coerce
gnu.expr.LambdaExp$Closure to java.lang.Runnable
at gnu.bytecode.ObjectType.coerceFromObject(ObjectType.java:180)
at gnu.kawa.functions.Convert.apply2(Convert.java:38)
at gnu.mapping.Procedure2.applyToObject(Procedure2.java:62)
at java.lang.reflect.Method.invoke(Native Method)
at gnu.mapping.CallContext$ReflectMethodHandle.invokeExact(CallContext.java:726)
at gnu.mapping.Procedure.applyToConsumerDefault(Procedure.java:75)
at java.lang.reflect.Method.invoke(Native Method)
at gnu.mapping.CallContext$ReflectMethodHandle.invokeExact(CallContext.java:726)
at gnu.mapping.CallContext.runUntilDone(CallContext.java:586)
at gnu.mapping.CallContext.getFromContext(CallContext.java:616)
at gnu.expr.Expression.eval(Expression.java:52)
at gnu.expr.ApplyExp.apply(ApplyExp.java:161)
at gnu.expr.LetExp.apply(LetExp.java:72)
at gnu.expr.ModuleExp.evalModule2(ModuleExp.java:281)
at gnu.expr.ModuleExp.evalModule(ModuleExp.java:211)
at kawa.Shell.run(Shell.java:283)
at kawa.Shell.run(Shell.java:196)
at kawa.Shell.run(Shell.java:183)
at kawa.TelnetRepl.apply0(TelnetRepl.java:25)
at gnu.mapping.RunnableClosure.run(RunnableClosure.java:75)
at java.lang.Thread.run(Thread.java:764)
Per Bothner
2017-09-17 15:59:01 UTC
Permalink
Post by Sonny To
this works
(let ((handler (android.os.Handler (android.os.Looper:getMainLooper)))
)
(handler:post (lambda ()
(android.util.Log:i "scm" "run me")))
)
this is non-intuitive. why doesn't it work if i bind the lambda to a
symbol? seems like a bug to me
Perhaps a documentation bug: It may not be clear that when the manual
say "required type" in:

This is possible when the required type is an interface or
abstract class with a Single (exactly one) Abstract Methods.

we mean the type required by the *compile-time context* of the expression.

This is a deliberate design limitation, not a bug. This way the anonymous
class can be created a compile-time, not run-time. Doing it at run-time
would be more difficult and more expensive. Kawa has always put a high
priority on performance.

Perhaps a future Kawa extension will allow "SAM-conversion" at run-time,
but it is not currently in the plans.
--
--Per Bothner
***@bothner.com http://per.bothner.com/
Sonny To
2017-09-17 16:03:55 UTC
Permalink
Thanks for the explanation. However, I'm still not clear about
compile-time. I'm evaluating this in the TelnetRepl. its interpreted
no?
Post by Per Bothner
Post by Sonny To
this works
(let ((handler (android.os.Handler (android.os.Looper:getMainLooper)))
)
(handler:post (lambda ()
(android.util.Log:i "scm" "run me")))
)
this is non-intuitive. why doesn't it work if i bind the lambda to a
symbol? seems like a bug to me
Perhaps a documentation bug: It may not be clear that when the manual
This is possible when the required type is an interface or
abstract class with a Single (exactly one) Abstract Methods.
we mean the type required by the *compile-time context* of the expression.
This is a deliberate design limitation, not a bug. This way the anonymous
class can be created a compile-time, not run-time. Doing it at run-time
would be more difficult and more expensive. Kawa has always put a high
priority on performance.
Perhaps a future Kawa extension will allow "SAM-conversion" at run-time,
but it is not currently in the plans.
--
--Per Bothner
Per Bothner
2017-09-17 16:50:02 UTC
Permalink
Post by Sonny To
Thanks for the explanation. However, I'm still not clear about
compile-time. I'm evaluating this in the TelnetRepl. its interpreted
no?
Yes and no. "Compile-time" is when the Scheme expression is parsed,
analyzed, and (normally) translated to JVM bytecode. On most platforms,
even when you type an expression into a REPL, it gets translated to bytecode
and then loaded on-the-fly using java.lang.ClassLoader.defineClass.

Because Android doesn't have ClassLoader.defineClass (because it uses dex),
we don't translate REPL input to bytecode. Instead, there is an
interpreter that evaluates to tree (Expression) form of the expression.

Determining when "SAM-conversion" (converting lambda to anonymous class)
is allowed is done at compile-time. On most platforms, creating the
anonymous class is also done at time-time. On Android, the last step is
deferred to run-time, using a ProcedurallProxy class, but that only supports
converting a lambda to an interface, not a class.

It probably wouldn't be difficult to use ProcedurallProxy at run-time to convert
a procedure value to a class instance. However, it would require changes to
error handling: Instead of compiling a simple cast, we have to compile to a more
complex (and slower) procedure. There is also a question of what warning messages,
if any to emit. Perhaps worth revisiting if/when we implement
"Optional strict typing along with an explicit dynamic type"
(https://www.gnu.org/software/kawa/Ideas-and-tasks.html#Optional-strict-typing-along-with-an-explicit-dynamic-type).

(The 'dynamic' type is already partially supported.)
--
--Per Bothner
***@bothner.com http://per.bothner.com/
Sonny To
2017-09-17 18:54:29 UTC
Permalink
this is a big problem for my use case...

i wouldn't be able to do something like this on android at runtime

(object (java.lang.Runnable)
((run (a ::void))::void
(+ 1 1)
))
Post by Per Bothner
Post by Sonny To
Thanks for the explanation. However, I'm still not clear about
compile-time. I'm evaluating this in the TelnetRepl. its interpreted
no?
Yes and no. "Compile-time" is when the Scheme expression is parsed,
analyzed, and (normally) translated to JVM bytecode. On most platforms,
even when you type an expression into a REPL, it gets translated to bytecode
and then loaded on-the-fly using java.lang.ClassLoader.defineClass.
Because Android doesn't have ClassLoader.defineClass (because it uses dex),
we don't translate REPL input to bytecode. Instead, there is an
interpreter that evaluates to tree (Expression) form of the expression.
Determining when "SAM-conversion" (converting lambda to anonymous class)
is allowed is done at compile-time. On most platforms, creating the
anonymous class is also done at time-time. On Android, the last step is
deferred to run-time, using a ProcedurallProxy class, but that only supports
converting a lambda to an interface, not a class.
It probably wouldn't be difficult to use ProcedurallProxy at run-time to convert
a procedure value to a class instance. However, it would require changes to
error handling: Instead of compiling a simple cast, we have to compile to a more
complex (and slower) procedure. There is also a question of what warning messages,
if any to emit. Perhaps worth revisiting if/when we implement
"Optional strict typing along with an explicit dynamic type"
(https://www.gnu.org/software/kawa/Ideas-and-tasks.html#Optional-strict-typing-along-with-an-explicit-dynamic-type).
(The 'dynamic' type is already partially supported.)
--
--Per Bothner
Per Bothner
2017-09-17 20:02:16 UTC
Permalink
Post by Sonny To
this is a big problem for my use case...
i wouldn't be able to do something like this on android at runtime
(object (java.lang.Runnable)
((run (a ::void))::void
(+ 1 1)
))
You can probably work around it with:

(->java.lang.Runnable
(lambda ()::void (set! i (+ i 1))))

BTW - I assume (a ::void) is just a typo. Kawa should probably complain about it.
--
--Per Bothner
***@bothner.com http://per.bothner.com/
Sonny To
2017-09-18 06:35:17 UTC
Permalink
yes that was an error. copy and paste of testing code. how would I
implement an interface with multiple methods?
Post by Per Bothner
Post by Sonny To
this is a big problem for my use case...
i wouldn't be able to do something like this on android at runtime
(object (java.lang.Runnable)
((run (a ::void))::void
(+ 1 1)
))
(->java.lang.Runnable
(lambda ()::void (set! i (+ i 1))))
BTW - I assume (a ::void) is just a typo. Kawa should probably complain about it.
--
--Per Bothner
Sonny To
2017-09-18 08:16:57 UTC
Permalink
It seems implementing single method interface using lambda only works
with methods and not in constructors?

for example, this fails

#|kawa:137|# (java.lang.Thread (lambda ()
(+ 1 1)))
#|.....138|# /dev/stdin:137:1: warning - no possibly applicable method
'<init>/valueOf' in java.lang.Thread
gnu.mapping.WrongArguments
at gnu.mapping.MethodProc.matchFailAsException(MethodProc.java:136)
at gnu.kawa.reflect.Invoke.applyToObject(Invoke.java:264)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at gnu.mapping.CallContext$ReflectMethodHandle.invokeExact(CallContext.java:726)
at gnu.mapping.Procedure.applyToConsumerDefault(Procedure.java:75)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at gnu.mapping.CallContext$ReflectMethodHandle.invokeExact(CallContext.java:726)
at gnu.mapping.CallContext.runUntilDone(CallContext.java:586)
at gnu.expr.ModuleExp.evalModule2(ModuleExp.java:342)
at gnu.expr.ModuleExp.evalModule(ModuleExp.java:211)
at kawa.Shell.run(Shell.java:283)
at kawa.Shell.run(Shell.java:196)
at kawa.Shell.run(Shell.java:183)
at kawa.repl.processArgs(repl.java:714)
at kawa.repl.main(repl.java:820)
Post by Sonny To
yes that was an error. copy and paste of testing code. how would I
implement an interface with multiple methods?
Post by Per Bothner
Post by Sonny To
this is a big problem for my use case...
i wouldn't be able to do something like this on android at runtime
(object (java.lang.Runnable)
((run (a ::void))::void
(+ 1 1)
))
(->java.lang.Runnable
(lambda ()::void (set! i (+ i 1))))
BTW - I assume (a ::void) is just a typo. Kawa should probably complain about it.
--
--Per Bothner
Per Bothner
2017-09-18 13:58:15 UTC
Permalink
Post by Sonny To
It seems implementing single method interface using lambda only works
with methods and not in constructors?
for example, this fails
#|kawa:137|# (java.lang.Thread (lambda ()
(+ 1 1)))
#|.....138|# /dev/stdin:137:1: warning - no possibly applicable method
'<init>/valueOf' in java.lang.Thread
gnu.mapping.WrongArguments
Yes and no. Method selection uses different code than the code that checks
for type compatibility. It's mostly the same tests, but the former doesn't
handle lambda-to-interface conversion. It would be non-trivial to change that.

However, this works:

(java.lang.Thread (->java.lang.Runnable (lambda () (newline))))
--
--Per Bothner
***@bothner.com http://per.bothner.com/
Sudarshan S Chawathe
2017-09-18 11:22:09 UTC
Permalink
how would I implement an interface with multiple methods?
I have found that simply defining the methods required by an interface
(with the proper names and type signatures) works, in the sense that the
resulting objects can be used anywhere that requires objects
implementing the interface.

Regards,

-chaw
Per Bothner
2017-09-18 14:22:56 UTC
Permalink
Post by Sudarshan S Chawathe
how would I implement an interface with multiple methods?
I have found that simply defining the methods required by an interface
(with the proper names and type signatures) works, in the sense that the
resulting objects can be used anywhere that requires objects
implementing the interface.
Not sure I understand what you mean by that.

The problem is defining an instance of an interface:
(1) with multiple methods;
(2) on-the-fly, in a REPL;
(3) on Android, which doesn't (didn't?) have ClassLoader.defineClass.

It should be possible to generalize gnu.kawa.reflect.ProceduralProxy
to handle multiple methods and corresponding implementing procedures.
The tricky is specifying which method is implemented by which procedure.

Perhaps problem (3) above is no longer a problem, at least on Android 8.
https://developer.android.com/reference/java/lang/ClassLoader.html
says that the byte array to defineClass "should have the format of a valid
class file as defined by The Java™ Virtual Machine Specification."

So maybe we need to update the compilerAvailable test in ModuleExp.java.
If we now can generate classes on-the-fly then that removes a major
limitation of Kawa on Android. (At least on newer Android versions.)
--
--Per Bothner
***@bothner.com http://per.bothner.com/
Sudarshan S Chawathe
2017-09-18 14:31:06 UTC
Permalink
Mea culpa. I temporarily lost sight of the fact that the discussion was
in the context of a REPL on Android.

-chaw
Subject: Re: using lambda to implement one method interface
Date: Mon, 18 Sep 2017 07:22:56 -0700
Post by Sudarshan S Chawathe
how would I implement an interface with multiple methods?
I have found that simply defining the methods required by an interface
(with the proper names and type signatures) works, in the sense that the
resulting objects can be used anywhere that requires objects
implementing the interface.
Not sure I understand what you mean by that.
(1) with multiple methods;
(2) on-the-fly, in a REPL;
(3) on Android, which doesn't (didn't?) have ClassLoader.defineClass.
It should be possible to generalize gnu.kawa.reflect.ProceduralProxy
to handle multiple methods and corresponding implementing procedures.
The tricky is specifying which method is implemented by which procedure.
Perhaps problem (3) above is no longer a problem, at least on Android 8.
https://developer.android.com/reference/java/lang/ClassLoader.html
says that the byte array to defineClass "should have the format of a valid
class file as defined by The Java™ Virtual Machine Specification."
So maybe we need to update the compilerAvailable test in ModuleExp.java.
If we now can generate classes on-the-fly then that removes a major
limitation of Kawa on Android. (At least on newer Android versions.)
--
--Per Bothner
Loading...