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

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.

17 comments:

Aaron Heckmann said...

I completely agree. I love using arguments.callee and your examples are excellent. It makes me not want to "use strict".

Zmitro Lapcjonak said...

I follow your posts and agree with you about need to leave the arguments.callee even in the strict mode.

I read the thread in next JS authors where they discussed the POLA problem.

What is the current state of this decision? Have they considered the complaints of many JS-developers?
Are they going to re-design the next JS version?

Andrea Giammarchi said...

In my opinion, the POLA problem is like the eval one. If you have no control of your code or you do not safe stuff, it cannot be a language specs problem. That's it

Lars Gunther (itpastorn) said...

I posted your concerns to the ES WG mailing list today, FWIW.

Jorge said...


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


Unless I'm missing something... (likely):

elegant #2:
var f= function f (i) { return i<2 ? i : i+ f(i-1); };

elegant #3:
var f= eval("( function f (i) { return i<2 ? i : i+ f(i-1); } )");


--
Jorge.

Jorge said...

That the current versions of IE have issues with named function expressions (too) should not be an argument here as those are not ES5s.

We're all hoping that M$ won't be brave enough to release their ES5-IE without fixing that first, and the GC/circular references issues, and the... etc, unless, of course, they're after shrinking IE's marketshare figures (even more).

My $0.02.
--
Jorge.

Andrea Giammarchi said...

@Jeorge, you missed the whole point about pre-compiled function in your first example, while eval could be even disabled in some environment (Rhino, others) ... so yes, I guess you missed something.
Finally, named function is totally redundant in configuration objects and missed IE capability means every library has to be written in 2 completely different version? Seriously, I still do not get what is wrong with callee.

var f = arguments.callee || callee;

this should be the way to retrieve callee in "use strict"; imho.

Jorge said...

... you missed the whole point about pre-compiled function in your first example ...

You too, in:

function (i) { return i<2 ? i : i+ arguments.callee(i-1); }

what's that gets "pre-compiled" ?

:-)

... named function is totally redundant in configuration ...

Of course not as long as you need to reference it. Just name them and use the name... it's shorter than arguments.callee. And faster. And equivalent. And using the "arguments" object is expensive and precludes optimization:

http://groups.google.com/group/comp.lang.javascript/browse_thread/thread/1919a68bd6fea63e
http://jorgechamorro.com/cljs/030/

... and missed IE capability ...

Then shouldn't IE recover this "missed capability" in time for ES5 ?

... every library has to be written in 2 completely different versions ...

Oh, please, don't use (any) of IE's bugs as an argument for this.

--
Jorge.

Andrea Giammarchi said...

Man, did you read the post or you just quickly srolled examples?
Let's put in this way, ok?

var somewhereElseInScope = 5;
var f = Function("i", "return i < " + somewhereElseInScope + "? i : i + arguments.callee(i-1);");

now do you understand pre-compiled function?

Named functions and redundancy, so your suggestion is to call evey useful function function callee(){} in order to solve the problem right? And two libraries from the scratch to avoid IE problems, right?

Then shouldn't IE recover this "missed capability" in time for ES5 ?
I though the main reason ECMAScript switched to 5, and "use strict"; is a string rather than adirective, is to avoid to break the web which still has to deal with Internet Explorer 6 ... so you are telling us that new ECMAScript features will not be usable until every currently used version of Internet Explorer will disappear? Are you talking about 2020, right? Good Stuff!

Andrea Giammarchi said...

P.S. read precedent post about arguments weirdness, I perfectly know it is expensive, but we are talking about JavaScript for God sake, not C++!

Jorge said...

var somewhereElseInScope = 5;
var f= (Function ("return function f (i) { return i <" + somewhereElseInScope + " ? i : i+ f(i-1); };"))();

Still, you could as well just use window.eval() or eval().

--
Jorge.

Andrea Giammarchi said...

Jorge, you are using Function as eval, and each creation will require two functions creation rather than one, the anonymous, and the internal one. I already wrote there are alternatives, you do not need to post code that does not reply to my points ... sorry for that.

Jorge said...

... Named functions and redundancy, so your suggestion is to call evey useful function function callee(){} in order to solve the problem right? And two libraries from the scratch to avoid IE problems, right? ...

Wrap the named functions in anonymous ones and that will work everywhere, even in IE. Just a single version. Like this:

var aNamedFunction= (function(){
return function aNamedFunction () {};
})();

See:
http://groups.google.com/group/comp.lang.javascript/browse_thread/thread/7301d0e87cb1bbb0/e9ac38a88602cf32#e9ac38a88602cf32

--
Jorge.

Jorge said...

... sorry for that.

Sorry for spoiling ur fairy tales.
And happy banning this 1...

--
Jorge.

Andrea Giammarchi said...

Jorge, I have never banned a single comment in this blog, except spam, but trust me, it is systematic that once a month somebody discover this blog and starts to write comments as the one who knows everything thinking "Who is this guy? He surely knows less than me".
If you would have followed me before, you could have noticed that whatever you are writing here is not new to me. Have a look into the first part of this probably lost battle, to discover that the wrapping stuff is the first obvious thing to do, and that at the same time it underlines my points about bigger sized libraries, less elegant code to both read and write, JavaScript beauty gone, and double functions with double scope resolutions in core to manage.
Now, if you would be so kind to realize that I started to write JS 10 years ago and this blog is up since 2006 and it is not the first one I write, maybe you could consider that reading stuff, before blaming without knowing the author and with pointless arrogance, is better than re-discover the well each reply. Don't you agree?
Now if you want think about my points in Part I and Part II, and if you have a better solutions than others already commented/posted about, I will truly appreciate it.
Regards

Andrea Giammarchi said...

P.S. ... the wrap is still this case I commented the reply before: each creation will require two functions creation rather than one, the anonymous, and the internal one. It took a reply to understand pre-compiled function point ... it makes sense to read and sometimes to think before a post, or it is just a waist of time, for both you and me, no? Hope next one will not be polemic, or a link to a 3d that I already read time ago, thank you for understanding. I'd like to keep a bit of quality in this ugly blog. Regards

Andrea Giammarchi said...

P.S. I am in twitter, WebReflection is the nick