Thursday, January 7, 2010

Scala Supports Non-Local Returns

Writing some Scala code today, I found myself using non-local returns without even thinking about it. After realizing what I had done, I dug a little deeper to see what was really going on.

Take this completely made up, nonsensical example:


object Foo {
def main(args: Array[String]) {
foo(List(1, 2, 3))
}

def foo(l: List[Int]): Int = {
l.foreach { (i) =>
println(i)
return 5
}
return 10
}
}


This code will print "1" and then exit.

Perhaps this is obvious, that the "return 5" applies to the "foo" method, so the values 2 and 3 in "l" will not have a chance to be printed.

However, think about what is going on under the covers--Scala is passing the foreach method an anonymous inner class with a "void apply(int i)" method. And inside of that "apply" method is the code between the "{ (i) => ... }".

So, how does code inside of the "apply" method cause its caller to perform an early exit, without the "foreach" even knowing about it?

Exceptions.

Here is the decompiled version of "print":


public int print(List l) {
Object localObject = new Object();
int exceptionResult1 = 0;
try {
l.foreach(new AbstractFunction1() {
public static final long serialVersionUID = 0L;

public final Nothing. apply(int i) {
Predef..MODULE$.println(BoxesRunTime.boxToInteger(i));
// here is the "return"--it puts "5" into an exception
throw new NonLocalReturnException(
this.nonLocalReturnKey1$1,
BoxesRunTime.boxToInteger(5));
}
});
return 10;
} catch (NonLocalReturnException localNonLocalReturnException) {
if (localNonLocalReturnException.key() == localObject) {
// get "5" back out of the exception
return BoxesRunTime.unboxToInt(localNonLocalReturnException.value());
}
throw localNonLocalReturnException;
}
}


I think the decompiler got a little confused with "nonLocalReturnKey", but you can see the basic idea is that any early return inside of a closure is converted into an exception that is then caught outside of the closure where a proper return call can be done.

I personally think this is handy, once you know what is going on. But from what I've picked up, any closures that make it into Java 7 will not support non-local returns and instead disallow the "return" keyword inside of closures. Which, I guess, at this point any Java closures are better than no closures at all.

4 comments:

Ittay Dror said...

what decompiler did you use?

Stephen Haberman said...

Hi Ittay,

After being a long-time fan of jad, lately I've been using JD:

http://java.decompiler.free.fr/

Ittay Dror said...

I've tried it, but was unable to get decompiled code as you have shown. Classes have MODULE$ constants in them, it refuse to open companion objects or annonymous function classes, etc.

Stephen Haberman said...

@Ittay As I recall, I may have had to open the companion .class file and anonymous function .class files manually with JD-GUI. E.g. just open Foo.class did not automatically have it realize Foo$/etc. should also be opened.

I went to verify this but unfortunately jd-gui isn't running at all on my machine right now. Not sure what is going on.