Deprecation of arguments.callee: how to deal with the case of instances of Function constructor?

RESOLVED INVALID

Status

()

Core
JavaScript Engine
RESOLVED INVALID
6 years ago
6 years ago

People

(Reporter: pincopalla00, Unassigned)

Tracking

({js1.7})

10 Branch
js1.7
Points:
---

Firefox Tracking Flags

(Not tracked)

Details

(Reporter)

Description

6 years ago
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();
(Reporter)

Updated

6 years ago
Keywords: js1.7
OS: Linux → All
Hardware: x86 → All

Comment 1

6 years ago
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)'.
(Reporter)

Comment 2

6 years ago
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?
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.
Status: UNCONFIRMED → RESOLVED
Last Resolved: 6 years ago
Resolution: --- → INVALID
(Reporter)

Comment 4

6 years ago
@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!
(Reporter)

Comment 5

6 years ago
If you are interested in this debate, it continues here: https://bugs.ecmascript.org/show_bug.cgi?id=263
You need to log in before you can comment on or make changes to this bug.