Monday, June 22, 2009

Do Not Remove arguments.callee - Part II

Few days ago kangax demystified named function expressions, underling the importance of the read only function name property and how messy is Internet Explorer JScript engine with named function and their scope.

Before that post, I wrote one about ECMAScript 5 decision to remove arguments.callee when "use strict"; is present (note in the link: it is not deprecated right now, it is an arguments property)

How These Two Things Are Related

While kangax post was more focused about possible leaks and unexpected functions definitions, I would like to grab more attention about what this problem will cause in tomorrow libraries and JavaScript usage. Removing arguments.callee, our code size, apparently faster in core thanks to this missed variable, will increase drastically and a big part of the beauty of JavaScript language will disappear with this callee decision.

The Classic Configuration Object Problem

How many libraries base constructors via configuration objects?
jQuery Ajax ?

$.ajax({
url:"page.server",
success:function(data){
if(data){
$.ajax({
url:"fallback.server",
success:arguments.callee
});
} else
alert("everything is OK");
}
});

NO? Simple enough, but of course I could use the "long kangax procedure" to wrap that function to avoid IE problems ... and in any case, what about two Ajax calls in the same page?

$.ajax({
url:"page.server",
success:function success(data){
// whatever will be
// .... but don't ya think it is a bit
// redundant? How will be our code style,
// worse than an open close XML tag?
}
});

// second call ...
$.ajax({
url:"page.server",
success:function success(data){
// which one will be the
// function success in
// Internet Explorer ???
}
});

More comes with User Interface libraries, take ExtJS as example ...

new Ext.Panel({
renderTo:document.body,
listeners:{
render:function(Panel){
// do some stuff ... and ...
Panel.removeListener("render", arguments.callee);
}
}
});

Above snippets is so simple that ECMAScript 5 better decided to transform it into something like this:

new Ext.Panel({
renderTo:document.body,
listeners:{
// not suitable for IE and multiple
// Panel creations ...
render:function render(Panel){
Panel.removeListener("render", render);
}
}
});

// suitable for IE as well
new Ext.Panel({
renderTo:document.body,
listeners:{
render:function(Panel){
var callee = Panel.initialConfig.listeners.render;
// note how long and boring is precedent string/operation

Panel.removeListener("render", callee);
}
}
});

In few words, we have to forget shortcuts to add and remove listeners for any kind of configuration object. Nice, isn't it?

The Classic Pre Compiled Function Problem

Another problem comes with Function constructor, able to create an anonymous function (lambda) via string and a preferred solution over eval calls. A pre compiled function is something created once and executed at "native speed" for each call. Example:

// classic function
function circle(r){
return r * r * Math.PI;
};

// pre compiled one
var circle = Function("r", "return r * r * " + Math.PI);

The main difference between above functions is that the first one needs to discover in the scope the Math "variable" plus its PI parameters for each call, while the second one will be exactly the function:

function anonymous(r) {
return r * r * 3.141592653589793;
}

which is about 1.6 to 2 times faster than the other one .... cool? Perfect, now try to imagine that our pre-compiled function is more complicated and inside a closure ... how the hell do you think to retrieve the function itself?

(function(){
// my library scope

// this one will generate an errror
// Function evaluates in a global scope
var f = Function("i", "return i < 2 ? i : i + f(i - 1)");

// we have only two options here ...
// the elegant one:
var f = Function("i", "return i < 2 ? i : i + arguments.callee(i - 1)");

// or the horrible namespace conflicts prone one ...
var hopefullyNoProblems = "myLib" + (""+Math.random()).slice(2);
window[hopefullyNoProblems] =
Function("i", "return i < 2 ? i : i + window." + hopefullyNoProblems + "(i - 1)");

})();

Cool enough? That function will be extremely slower than a pre-compiled one due to the global window object which is a variable not that fast to retrieve.
Moreover, since they decided that null or undefined should not be considered as window object, the usage of this of run-time called function is not a shortcut anymore, is it?

As Summary

This is my last chance to explain the importance of arguments.callee which is NOT the reason JavaScript is less secure, it is only a pain in the ass for JS engines developers as is arguments variable itself, the magic one which apparently is going to disappear as well.
What I do not understand, is why they do not make callee a reserved keyword and put this variable inside the function body as is for arguments and the function name.

function itsEasy(i){
// itsEasy lives only here in every browser except IE
// arguments lives only here in every browser
// so it is simple to put itsEasy, arguments,
// but so many problems with a callee variables ?
return i < 2 ? i : i + callee(i - 1);
};

We'll see how much we will have to change our code and how bigger will be for this non-sense decision ... JavaScript beauties should not be changed, even PHP has at least a function to retrieve the arguments array ... put a function then, whatever other solution, but please do not constrict developers to name in a completely meaningless and redundant way every single lambda. Not a single lambda based language is that fossy about it, IMHO.

No comments:

Post a Comment