My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Saturday, May 23, 2009

[ECMAScript 5] Do Not Remove arguments.callee !

Being subscribed in dunno how many developers mailing list, I completely forgot to follow up the arguments and arguments.callee discussion.
Accordingly to this John post, is with extreme surprise that I am discovering how critical is the gap between programming language developers and programming languages active users.

Even if I read more than once piece of SpiderMonkey or Google Chrome, I am part of programming languages users and I can tell you for sure that the decision to remove arguments.callee from the language will be a complete mess.

The Main Aim Of ECMAScript 5: Do Not Break What We Have So Far

Unfortunately, Internet Explorer will be broken, because named function persists in the scope, causing naming conflicts everywhere!


setTimeout(function f(){
alert(window.f);
}, 1000);

Above code will alert the function f in every Internet Explorer. Moreover, as you can read in one comme of John's entry, Internet Explorer has function interpretation "problems", declaring or overriding already declared function even if these are in an else case, while other browsers do not even consider that part of code if it will never be executed.

Another Attemp To Slow Down The Already Slow Browser


Reading this long thread about the discussion, some clever guy suggested a simple solution to avoid naming conflicts ... a closure

(function(){
setTimeout(function f(){
alert(window.f);
}, 1000);
})();

This simply means that our code size will drastically increase without a valid reason and number of created scopes and functions will be twice as was before (already too big). They think it will not slow down performances but they are forgetting old browsers that will be still used when all this wonderful shiny JavaScript will be released: Internet Explorer 7, and Internet Explorer 8 (assuming Internet Explorer 6 does not exist anymore ... would be nice to start to fight against the version 7 and then 8 ...).

Introspection and BackTrace? They Decided It Is A Bad Thing!


Without arguments.callee we are loosing arguments.callee.caller as well, often the only way we have to truly debug our code. At the same time the dynamic nature of JavaScript where "you can inject whatever wherever" will loose a big portion of its malleability.

Everything For Performances Reason ???


Apparently what this "new" language would like to get rid off is arguments variable, probably the most used variable ever, the inspiration of every ArrayLike object or library. This would be for performances, where we need as first step to call 2 functions rather than one for each runtime function assignment (setTimeout example and every assigned inline lambda) and where everybody will try to overload the language itself providing other methods to emulate the good old stuff!

Let's Start To Think About Re-Implementation Of Callee

As soon as I realized the news, I decided to think how to put again, and against performances, the callee behavior.

function F(original){
return function(){
var f = window.callee,
r = (callee = original).apply(this, arguments)
;
window.callee = f;
return r;
};
};

Above snippet execution time will be almost the same of a closure with mainly two advantages

  • less code to write

  • more performances when needed


Here an example:

var i = 0;
setTimeout(F(function(){
if(i++ === 1)
alert("OK");
else
setTimeout(callee /* please read more */, 1000);
}), 1000);

Since JavaScript is single thread, the instant that lambda will be called the global variable callee will refer to the lambda itself. Cool, but it is not enough.
To be sure that we do not loose the callee because of some other function assignment, we should trap again the callee inside that execution:

var i = 0;
setTimeout(F(function(){
if(i++ === 1)
alert("OK");
else
setTimeout(F(callee), 1000);
}), 1000);

There we are, a quick and dirty solution to the problem ... but, in this case for each timeout we are creating another function within its scope. This does not happen if we use the closure strategy so, again, we are trapped and we need to add more layers in the middle to increase responsiveness for something designed to improve performances ... does it make any sense?

function F(original){
function callee(){
// save the current window.callee
var f = window.callee, r;
// assign this callee function
window.callee = callee;
// retrieve the result calling original
// in the original scope, callee will be
// this whole function
r = original.apply(this, arguments);
// assign back the window callee
window.callee = f;
// return the result
return r;
};
// callee.callee will be the lambda
// rather than its wrap (callee itself)
callee.callee = original;
// here we are
return callee;
};

With above "monster" we have a slightly slower function that will always reassign the correct callee whatever will happen in the original function scope. This meanse simply that we can now use directly callee:

var i = 0;
setTimeout(F(function(){
if(i++ === 1)
alert("OK");
else
setTimeout(callee, 1000);
// callee.callee for direct access to the lambda itself
}), 1000);


I Bloody Hate This Idea

... but if they do not change their mind, it will be one of few other possibilities we have to avoid naming pollution everywhere causing conflicts nightmare with every Internet Explorer version less than 9.

Have fun with ECMAScript 5 and its brilliant strict mode.

6 comments:

Lars Gunther (itpastorn) said...

Why not bring your view(s) to the mailing list?

https://mail.mozilla.org/listinfo/es5-discuss

Andrea Giammarchi said...

I guess it is too late :(

Paul Irish said...

This is a huge step backwards. Callee should be kept for sure. Is anyone aware of the supporting argument to remove it?

Maian said...

See the ES working group's discussion on this issue: https://mail.mozilla.org/pipermail/es-discuss/2009-March/008970.html

Essentially, it's common to pass on the arguments object to another function (e.g. for parsing the arguments), but it's not intended to allow that other function to modify arguments.callee.

For example:

function calleePolluter(args) {
args.callee = null;
}

(function(i) {
if (i ≶ 0)
return [];
calleePolluter(arguments);
return arguments.callee(i - 1).concat([i]);
})(5);

The anonymous function call should produce [0,1,2,3,4,5], but calleePolluter modifies arguments.callee, causing the recursion to fail.

Losing the ability to trace function calls via arguments.caller does suck - there's some discussion on standardizing Gecko's error.stack property or some equivalent.

Andrea Giammarchi said...

if that's the problem, callee not writable by default and that's it but still, both arguments and callee should not disappear, it will mean ruin JavaScript, IMO.

Maian said...

That's a good point. But still, passing arguments to another function allows that function to call args.callee when that may not be intended. The arguments object was meant only for arguments - nothing else.

There was some discussion on ES4 about a "this function" syntax that would replace arguments.callee, e.g. "this function()". Not sure if that's going to be in ES-Harmony. I think ES-Harmony strict mode will try to deprecate the arguments object in preference for spread and rest, because of TCP and legacy arguments issues.

In any case, this is hardly something that would "ruin JavaScript", considering that many libraries are already trying to conform to ES5 strict mode and many users aren't even aware of arguments.callee.