Proper Tail Calls, a definition
One of the things I get to do here at Adobe is help out with the drafting of the ECMAScript Edition 4 (ES4) language specification. Lately, there has been a flurry of discussion on the ES4-discuss list about one of the proposed new features, Proper Tail Calls (a.k.a. Proper Tail Recursion, but I'm going to call it PTC from now on). I thought this might give me a good opportunity to provide some background information about this feature and what it might mean for ActionScript developers in the future.
In case you don't know, ECMAScript is the language standard upon which JavaScript, JScript, ActionScript and many other languages are based. The current version of ECMAScript is the third edition, but a lot of work is going on for the fourth edition. So what I'm discussing here is not currently in ActionScript, but may one day be if PTC makes it into the fourth edition specification.
Today I'll start with a definition of PTC, and I'll follow up over the next few days with examples and further thoughts. You can find the ES4 proper tail calls proposal at the ecmascript.org website. I should warn you, though, that the proposals are geared more toward language implementors than users, so don't worry too much if you don't understand all the terminology they use. I'll try in this blog to translate what some of these proposals mean for ActionScript developers.
Speaking of writing geared toward computer scientists, the best definition of PTC I've come across is from a journal article by William Clinger at Northeastern University titled "Proper Tail Recursion and Space Efficiency". In that paper, he talks about the essence of PTC, which I'll paraphrase in terms of ActionScript below. I have to admit that much of the article is over my head, but he writes clearly so I learned a lot from reading the sections that I could wrap my brain around. He has the article posted on his website if you're interested in further reading (http://www.ccs.neu.edu/home/will/papers.html).
According to Professor Clinger, the essence of PTC is that it provides a new way to return from a function. Namely, instead of returning a value, you can call any other function as the last thing you do. Such a function call is said to be in tail position, or to make things easier you can just refer to the call as a "tail call". This may not seem like a big deal, but it does add a new twist to the life cycle of a function.
Consider a typical ActionScript function:
function foo () {
trace("hello");
}
The foo() function will exist in memory from the time that you call it to the time that it returns from that call. The runtime (I'm going to use the word "runtime" instead of "Flash Player" or "Virtual Machine") will store the function in a special area of memory called the stack. As the name implies, a stack is organized as a last-in first-out stack, like a deck of playing cards that grows and shrinks as you add, use, and discard cards from the top of the deck. Notice in our example that we call another function, trace(), from the body of foo(). This means that foo() remains in memory while the trace() function runs. In terms of the stack, the runtime stores trace() on top of foo(). This is not a big issue in our little example, but what if it turns out that trace() calls another function, and that function calls another function, and so on. Pretty soon your stack starts getting pretty big. In fact, sometimes your stack can get too large and you get a stack overflow error.
PTC changes this behavior if the function call is in tail position. Instead of keeping foo() in the stack and placing trace() on top of it, foo() is discarded and trace() takes its place. This seems like a sensible thing to do because foo() doesn't do anything after the call to trace(), so why keep it around in memory? (Some argue that keeping it around is really helpful for debugging, but we'll talk about that another day.) And if there's a tail call in trace(), that call replaces trace() in the stack and so on. Instead of an ever-growing stack, you get a stack that may not grow at all, which will make a stack overflow exception much less likely.
PTC's ability to increase stack-use efficiency leads some people to call this feature Tail Call Optimization (TCO). However, the motivation section of the ES4 proposal for PTC states:
"Proper tail calls are not an optimization, but an aspect of the semantics of the space usage of the language."
I hope that Professor Clinger's insight into the essence of PTC helps you see this as more than just a mere optimization. If you don't, it may be because I did a poor job of paraphrasing him, so I would encourage you to read his article. Over the next few days I'll post new entries that delve more deeply into PTC. Specifically, I'll devote separate posts to recursion and how PTC is useful for programming with finite state machines.
Comments
Great article. I think that PTC has something to do with the continuations present in functional languages. For example, you can call a function adding the name of another function as its last parameter, and such function will be called with the result of the first one. As far as I know, this is called Continuation Passing Style (CPS) in the context of functional programming. Not exactly the same, but pretty close. Interesting. What do you think?
Posted by: ruben | February 5, 2008 7:27 AM
Thanks. PTC definitely helps with Continuation Passing Style (CPS). If you use CPS in a language that does not have PTC, you run the risk of a stack overflow error because the call to the continuation doesn't return to the original function.
Posted by: Francis Cheng | February 5, 2008 9:03 PM
This is great news, Francis. PTC is one of the things I've been wanting in Actionscript for a while. Thanks for the info.
Posted by: Austin Haas | February 6, 2008 10:19 AM
Therefore, I'm wondering how PTC is used (if so) in relation to a recursive implementation of a function (e.g. factorial). Does PTC apply to the function call which is in tail position and its return value its not used? I'm thinking of:
def fact(n):
if (n == 1) return 1
return n * fact(n-1)
I'll have to read Clinger's paper and catch up with this language. :)
Posted by: ruben | February 6, 2008 1:32 PM
For more information about PTC and recursion, see my January 30th post Proper Tail Calls and Recursion. As for your example, the call to fact(n-1) is not in tail position because you are multiplying the return value of fact(n-1) by n, so that multiplication operation is the last thing the function does. See the link above for a lengthier discussion about that very function.
Posted by: Francis Cheng | February 7, 2008 9:19 AM
@Francis, came across your blog via Colin Moock's blog. Thanks for posting the ES4 happenings.
I found the description of proper-tail calls in "Programming in Lua" to be quite good. Though I did come in knowing about functions on the stack, something you did a good job of expounding here for the uninitiated.
Posted by: Nathan Youngman | February 8, 2008 4:13 PM