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) =>
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?


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) {
// here is the "return"--it puts "5" into an exception
throw new NonLocalReturnException(
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.


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:

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.