A VM in Java with tail-calls and continuations

Thursday, July 14, 2005

User Guide - Part Two

Yesterday I said I was going to show what tail-calls look like, not how they will look like. That's because nothing will be changed, except perhaps you, the programmer - who may start using them a lot more.

But what are tail-calls anyway? Tail-calls are function calls - or method calls, in Javanese - done in tail position. What the hell is tail position? Well, something is in tail position, if it's done just before you do a return.

Confusing? Lets see some examples of what this means in Java:

// first example
methodCall();
return;

// second example
return methodCall();

As you can see, tail-calls come in two shapes in Java: they're either a method call followed by a void return, or a return of a method call.

Is it that simple? Yep, pretty much. Well, no. In fact, tail-calls always look like this, but these aren't always tail-calls.

Imagine, in our first example above, methodCall returns an int. This int has to be discarded by the JVM, before control is returned to our parent method, so the method call isn't quite the last thing that's being done before the return.

Now take our second example, and consider that methodCall returns a float but the method we are returning from returns a double. The JVM must coerce the float returned by methodCall to a double before returning from this method, so again that function call isn't the last thing done before the return. Depending on your compiler (which is javac, most likely) some coercions do work (byte, short and char to int, and coercions amongst objects without casts).

All this details mean that, if you do come to depend on tail-calls, you must beware of these issues. Other, simpler, examples where “tail-calls don't work” are:
// first example
synchronized (obj) {
return methodCall();
}

// second example
try {
return methodCall();
} catch (Exception e) {
// ...
} finally {
// ...
}

Clearly, on both examples, there are things to be done after the method call - to release the lock, to handle exceptions and to run finallies - so these aren't really tail-calls.

But hey, what's so special about tail-calls? Well, remember all that stuff about them being the very last thing done in a method? If there's nothing more to be done in that method, you can simply jump to the other method and ignore the caller and its frame. This both reduces stack space, allows the earlier collection of some local variables, and promotes a recursion oriented programming style.

That's all there is to know about tail-calls. Tomorrow, continuations will follow.

No comments:

Archive