SAM conversion
Peter Levart
peter.levart at marand.si
Fri Jul 2 05:25:02 PDT 2010
Hello Maurizio!
#1 - I played a little with current prototype and noticed that this code:
package lambda;
public class SamTest1
{
public static abstract class AbstractRunnable1 implements Runnable
{
}
public static abstract class AbstractRunnable2 implements Runnable
{
}
public static void doRun1(AbstractRunnable1 r)
{
r.run();
}
public static void doRun2(AbstractRunnable2 r)
{
r.run();
}
public static void main(String[] args)
{
#void() lambda = #() {
System.out.println("Hello!");
};
doRun1(lambda);
doRun2(lambda);
}
}
...produces verification error when trying to run it:
Exception in thread "main" java.lang.VerifyError: Bad type on operand stack in method lambda.SamTest1.main([Ljava/lang/String;)V at offset 42
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:186)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:107)
I noticed this when I wanted to test the state of current SAM conversion functionality. As I found out it follows the straw-man proposal which has a number of problems that were discussed on this list.
I'll just summarize two of them:
#2 - the following code:
package lambda;
public class SamTest2
{
public static abstract class CountingRunnable implements Runnable
{
public int counter;
}
public static void doRun(CountingRunnable r)
{
System.out.print("Run #" + (++r.counter) + ": ");
r.run();
}
public static void main(String[] args)
{
#void() lambda = #() {
System.out.println("Hello!");
};
Runnable r1 = lambda;
Runnable r2 = lambda;
System.out.println("r1 == r2: " + (r1 == r2));
doRun(lambda);
doRun(lambda);
}
}
...prints:
r1 == r2: false
Run #1: Hello!
Run #1: Hello!
#3 - the following code:
package lambda;
import java.io.IOException;
public class SamTest3
{
public static abstract class SneakyRunnable implements Runnable
{
public SneakyRunnable() throws IOException
{
throw new IOException("sneaked!");
}
}
public static void main(String[] args)
{
#void() lambda = #() {
System.out.println("Hello!");
};
SneakyRunnable r = lambda;
}
}
...throws:
Exception in thread "main" java.io.IOException: sneaked!
at lambda.SamTest3$SneakyRunnable.<init>(SamTest3.java:15)
at lambda.SamTest3$1.<init>(Unknown Source)
at lambda.SamTest3.main(SamTest3.java:25)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:613)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:110)
Do you have any information about the direction Oracle will take to solve these problems? As these are problems of the specification, I can see why prototype is not solving them yet.
Anyway, I think they can be split into "light" and "serious" problems:
The "r1 == r2: false" is in the "light" category. We already have the same problem with auto-boxing of primitives and it is not causing any noticeable errors in practice.
The other two: "Run #1: Hello!, Run #1: Hello!" and "IOException: sneaked!" are more serious and I think that the specification will sooner or later have to take a stand on how to solve them.
The specification could take the stand that "any implicit conversion of function typed value to a SAM type always produces new instance of SAM object". This would simply mean that problems #2 are not acknowledged problems and problem #3 could be solved by having the compiler infer that "SneakyRunnable r = lambda;" statement can throw IOException. Many people would find this acceptable. I don't know yet if I'm one of them.
Alternately specification could take care of the problems by making the function type -> SAM type conversion "explicitly visible" with new syntax. This would, for the price of new syntax and some more typing, make browsing the code easier, since known code patterns would preserve semantics and new code patterns would explicitly speak their own semantics. Take for example the following syntax:
SamTypedExpression:
"new" FunctionTypedExpression
For example:
#void() lambda = #() {
System.out.println("Hello!");
};
Runnable r1 = new lambda;
Runnable r2 = new lambda;
doSomethingWithSneakyRunnable( new #(){ System.out.println("Hello!"); } );
This syntax has it's drawbacks. It's ambiguous in the following example:
public class ExplicitSamConversion
{
public static class Task implements Runnable
{
public void run() {
System.out.println("Hello from class!");
}
}
public static #void() Task()
{
return #() {
System.out.println("Hello from lambda!");
};
}
public static void main(String[] args)
{
Runnable r = new Task();
}
}
...but it could be disambiguated by compiler giving precedence to class instantiation when both class name and method name are in scope. The work-around for invoking method would simply be placing parentheses around method invocation:
Runnable r = new (Task());
In practice such clashes would be rare for those following Java naming conventions.
So, how is lambda specification doing so far?
Regards, Peter
More information about the lambda-dev
mailing list