Last Comment Bug 725398 - Deprecation of arguments.callee: how to deal with the case of instances of Function constructor?
: Deprecation of arguments.callee: how to deal with the case of instances of Fu...
Status: RESOLVED INVALID
: js1.7
Product: Core
Classification: Components
Component: JavaScript Engine (show other bugs)
: 10 Branch
: All All
: -- normal (vote)
: ---
Assigned To: general
:
Mentors:
Depends on:
Blocks:
  Show dependency treegraph
 
Reported: 2012-02-08 10:35 PST by pincopalla00
Modified: 2012-02-09 18:46 PST (History)
3 users (show)
See Also:
Crash Signature:
(edit)
QA Whiteboard:
Iteration: ---
Points: ---
Has Regression Range: ---
Has STR: ---


Attachments

Description pincopalla00 2012-02-08 10:35:49 PST
User Agent: Mozilla/5.0 (X11; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0
Build ID: 20120202211454

Steps to reproduce:

Early versions of javascript did not allow named function expressions, and for this reason you could not make a recursive function expression.

For example, this syntax worked:

    function factorial (n) {
     return !(n > 1) ? 1 : factorial(n - 1) * n;
    }

    [1,2,3,4,5].map(factorial);

but:

    [1,2,3,4,5].map(function (n) {
      return !(n > 1) ? 1 : /* what goes here? */ (n - 1) * n;
    });

did not. To get around this arguments.callee was added so you could do

    [1,2,3,4,5].map(function (n) {
      return !(n > 1) ? 1 : arguments.callee(n - 1) * n;
    });

However this was actually a really bad solution as this (in conjunction with other arguments, callee, and caller issues) make inlining and tail recursion impossible in the general case (you can achieve it in select cases through tracing etc, but even the best code is sub optimal due to checks that would not otherwise be necessary). The other major issue is that the recursive call will get a different this value, eg.

    var global = this;
    var sillyFunction = function (recursed) {
     if (!recursed) { return arguments.callee(true); }
     if (this !== global) { alert("This is: " + this); }
     else { alert("This is the global"); }
    }
    sillyFunction();

Anyhoo, EcmaScript 3 resolved this issues by allowing named function expressions. For example:

    [1,2,3,4,5].map(function factorial (n) {
      return !(n > 1) ? 1 : factorial(n-1)*n;
    });

This has numerous benefits

    * the function can be called like any other from inside your code
    * it does not pollute the namespace
    * the value of this does not change
    * it's more performant (accessing the arguments object is expensive)

Whoops, just realised that in addition to everything else the question was about arguments.callee.caller, or more specifically Function.caller. At any point in time you can find the deepest caller of any function on the stack, and as I said above looking at the call stack has one single major effect: It makes a large number of optimisations impossible, or much much more difficult. Eg. if you cannot guarantee that a function f will not call an unknown function it is not possible to inline f. Basically it means that any call site that may have been trivially inlinable accumulates a large number of guards, take:

    function f (a, b, c, d, e) { return a ? b * c : d * e; }

If the js interpreter cannot guarantee that all the provided arguments are numbers at the point that the call is made, it needs to either insert checks for all the arguments before the inlined code, or it cannot inline the function. Now in this particular case a smart interpreter should be able to rearrange the checks to be more optimal and not check any values that would not be used. However in many cases that's just not possible and therefore it becomes impossible to inline.


Actual results:

When you use the Function constructor there are not alternatives to argument.callee, so its deprecation could be a bug:

    function createPerson (sIdentity) {
        var oPerson = new Function("alert(arguments.callee.identity);");
        oPerson.identity = sIdentity;
        return oPerson;
    }

    var john = createPerson("John Smith");

    john();
Comment 1 Luke Wagner [:luke] 2012-02-08 11:00:58 PST
I think the question you are asking is: how do I write a recursive function when using the Function constructor without arguments.callee?  If so, it requires a bit of grossness, but you can write:

var fac = let (f = new Function('f', 'x', 'return x == 0 ? 1 : x * f(f, x-1)')) f.bind(null, f);

using 'bind' to allow callers to simply write 'fac(x)' instead of 'fac(fac, x)'.
Comment 2 pincopalla00 2012-02-08 12:12:44 PST
But... why complicate things?

Consider the following code i wrote recently:

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function#Example

I needed a shortcut to massively modify the DOM. If you want a synthax like this: selector(".myClass").changeSomething("property1", "Some text")("property2", "Another text")("property3", "etc. etc.").doSomething1().doSomething2().etc().etc(), the function selector() you created must return an executable object, i.e. an instance of Function. If you want each instance to recognize his own properties you must use arguments.callee within the function. And you have not alternatives to bind().
But if you have to create many functions dynamically, the use of Function.bind will double the number of functions!

Why complicate things?
Comment 3 Jeff Walden [:Waldo] (remove +bmo to email) 2012-02-08 12:33:20 PST
Because that's what the spec says.  Your argument's not with us, it's with the spec.

Since we're going to follow the spec, I think your complaint lies with them, not with us.  Feel free to contribute to the mailing list if you want:

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

If and when the spec says something other than what it says now, we'll update to include it.  But for now, I don't think there's anything here that we plan to change.
Comment 4 pincopalla00 2012-02-08 14:25:10 PST
@Jeff
Ok, I posted this bug here: https://bugs.ecmascript.org/show_bug.cgi?id=263.

The point is that an instance of Function constructor:

  * does not create a closure to the calling context;
  * can't access to the arguments.callee property (https://developer.mozilla.org/en/JavaScript/Strict_mode#Making_eval_and_arguments_simpler: «arguments.callee for strict mode functions is a non-deletable property which throws when set or retrieved»)

So it can't access to its own properties!!

It is much easier that you assign some properties to functions created with the Function constructor rather than to declared literally ones: in fact the creation of the first ones can be serialized, the creation of the other ones not. Consequently, the first ones are more close to objects with their properties, while having some custom properties for the second ones is more the exception than the norm. And it's absurd that the functions created by the Function constructor can't access to themselves and their properties!
Comment 5 pincopalla00 2012-02-09 18:46:16 PST
If you are interested in this debate, it continues here: https://bugs.ecmascript.org/show_bug.cgi?id=263

Note You need to log in before you can comment on or make changes to this bug.