Volunteers needed: Function.prototype.bind() in ES3.1 pseudo-code
2008/9/18 Mark S. Miller <erights at google.com>:
The Redmond mtg is fast approaching. We'd like to put out an official for-Redmond-mtg draft of the ES3.1 spec by then. I had volunteered to write the spec for Function.prototype.bind(). Long term, I think we all agree we'd like to see this spec and many others self-hosted in EcmaScript. However, the discussion of self-hosting issues makes clear that this ain't gonna happen in time for ES3.1.
So what we need now is a spec for Function.prototype.bind() in the peculiar pseudo-code style -- combining the worst of COBOL and assembly language --
What pseudo-code style? This:
Function.prototype.bind( context, [ preArg1 [, preArg1 [,...]]])
?
used in the rest of the spec. Unfortunately, I won't have time between now and then. Would anyone care to contribute some text? Please?
I'm not sure exactly what you're looking for so I've typed only a little. Is this in the right direction:-
================================================ Function.prototype.bind( context, [ preArg1 [, preArg1 [,...]]])
Returns a function that invokes this function. The |context| argument determines the execution context of the returned function. Arguments 1..n are used for values of parameter variables to this function.
Example: var brendan = { name : "Brendan" }; function sayHelloTo(name) { return "Hi " + name + ", my name is " + this.name + "."; }
brendansGreeting = sayHelloTo.bind(brendan);
brendansGreeting("Mark");
returns "Hi Mark, my name is Brendan."
Methods |Function.prototype.call| and |Function.prototype.apply| do not affect the |this| value of a bound function. Thus:
brendansGreeting.call(null, "Mark");
returns "Hi Mark, my name is Brendan."
[insert_algorithm_steps_here]
The |length| property of Function.prototype.bind is 1.
================================================ A self-hosted version: /**
- @param {Object} context the |this| value to be used.
- @param {arguments} [1..n] optional arguments that are
- prepended to returned function's call.
- @return {Function} a function that uses |context| as the this
- value for the returned function and arguments 1..n for values
- of parameter variables to this function. */ Function.prototype.bind = function(context) { var fn = this, ap, concat, args, isPartial = arguments.length > 1; // Strategy 1: just bind, not a partialApply if(!isPartial) { return function() { if(arguments.length !== 0) { return fn.apply(context, arguments); } else { return fn.call(context); // faster in Firefox. } }; } else { // Strategy 2: partialApply ap = Array.prototype, args = ap.slice.call(arguments, 1); concat = ap.concat; return function() { return fn.apply(context, arguments.length === 0 ? args : concat.apply(args, arguments)); }; } }; ================================================
?
Garrett
On Thu, Sep 18, 2008 at 1:41 PM, Garrett Smith <dhtmlkitchen at gmail.com> wrote:
2008/9/18 Mark S. Miller <erights at google.com>:
The Redmond mtg is fast approaching. We'd like to put out an official for-Redmond-mtg draft of the ES3.1 spec by then. I had volunteered to write the spec for Function.prototype.bind(). Long term, I think we all agree we'd like to see this spec and many others self-hosted in EcmaScript. However, the discussion of self-hosting issues makes clear that this ain't gonna happen in time for ES3.1.
So what we need now is a spec for Function.prototype.bind() in the peculiar pseudo-code style -- combining the worst of COBOL and assembly language --
What pseudo-code style? This:
The pseudo-code style from the existing ES3 spec. An example:
11.3.1 Postfix Increment Operator
The production PostfixExpression : LeftHandSideExpression [no
LineTerminator here] ++ is evaluated as follows:
1. Evaluate LeftHandSideExpression.
2. Call GetValue(Result(1)).
3. Call ToNumber(Result(2)).
4. Add the value 1 to Result(3), using the same rules as for the +
operator (see 11.6.3).
5. Call PutValue(Result(1), Result(4)).
6. Return Result(3).
Mike
On Thu, Sep 18, 2008 at 11:05 AM, Mike Shaver <mike.shaver at gmail.com> wrote:
On Thu, Sep 18, 2008 at 1:41 PM, Garrett Smith <dhtmlkitchen at gmail.com> wrote:
What pseudo-code style? This:
The pseudo-code style from the existing ES3 spec. An example:
11.3.1 Postfix Increment Operator
The production PostfixExpression : LeftHandSideExpression [no LineTerminator here] ++ is evaluated as follows:
- Evaluate LeftHandSideExpression.
...
Yes, that's exactly what I mean. Thanks.
On Thu, Sep 18, 2008 at 11:05 AM, Mike Shaver <mike.shaver at gmail.com> wrote:
On Thu, Sep 18, 2008 at 1:41 PM, Garrett Smith <dhtmlkitchen at gmail.com> wrote:
2008/9/18 Mark S. Miller <erights at google.com>:
OK. I think I got what you're saying now. I've taken the consideration that |arguments| may be deprecated. I also considered that it might seem desirable to bind non-functions that implement [[Call]] (possibly a host object).
makeDiv = Function.bind(document.createElement, document, "div");
Function.prototype.bind( context, [ preArg1 [, preArg1 [,...]]])
Returns a function that invokes this function. The |context| argument determines the execution context of the returned function. Arguments 1..n are used for values of parameter variables to this function.
- if this does not have a [[Call]] property, throw a TypeError
- let FUN = this
- if caller supplied more than one argument, go to step 6.
- create a FunctionExpression that performs the following steps: 4.1. Call the [[Call]] operation on FUN with the argument list provided by the caller. 4.2. return Result(4.1)
(as if by the statements: return Function.prototype.apply.call(thisFun, context, arguments); )
- return Result(4)
- let PREARGS = a copy of the argument list provided by the caller, starting from position 1
- create a FunctionExpression that performs the following steps: 7.1. Create an ArgumentList containing all of the items of PREARGS followed by the arguments supplied by the caller. 7.2. Call the [[Call]] operation on FUN, with Result(7.1) as the ArgumentList 7.3. return Result(7.2)
(as if by the statements: var PREARGS = Array.prototype.slice.call(arguments, 1); return Function.prototype.apply.call(thisFun, context, Array.prototype.concat.apply(PREARGS, arguments)); )
- return Result(7)
The length property of the bind method is 1.
Is this anywhere close to being what you're looking for?
On Thu, Sep 18, 2008 at 10:41, Garrett Smith <dhtmlkitchen at gmail.com> wrote:
2008/9/18 Mark S. Miller <erights at google.com>:
The Redmond mtg is fast approaching. We'd like to put out an official for-Redmond-mtg draft of the ES3.1 spec by then. I had volunteered to write the spec for Function.prototype.bind(). Long term, I think we all agree we'd like to see this spec and many others self-hosted in EcmaScript. However, the discussion of self-hosting issues makes clear that this ain't gonna happen in time for ES3.1.
So what we need now is a spec for Function.prototype.bind() in the peculiar pseudo-code style -- combining the worst of COBOL and assembly language --
What pseudo-code style? This:
Function.prototype.bind( context, [ preArg1 [, preArg1 [,...]]])
?
used in the rest of the spec. Unfortunately, I won't have time between now and then. Would anyone care to contribute some text? Please?
I'm not sure exactly what you're looking for so I've typed only a little. Is this in the right direction:-
================================================ Function.prototype.bind( context, [ preArg1 [, preArg1 [,...]]])
Returns a function that invokes this function. The |context| argument determines the execution context of the returned function. Arguments 1..n are used for values of parameter variables to this function.
Example: var brendan = { name : "Brendan" }; function sayHelloTo(name) { return "Hi " + name + ", my name is " + this.name + "."; }
brendansGreeting = sayHelloTo.bind(brendan);
brendansGreeting("Mark");
returns "Hi Mark, my name is Brendan."
Methods |Function.prototype.call| and |Function.prototype.apply| do not affect the |this| value of a bound function. Thus:
brendansGreeting.call(null, "Mark");
returns "Hi Mark, my name is Brendan."
[insert_algorithm_steps_here]
The |length| property of Function.prototype.bind is 1.
================================================ A self-hosted version: /**
- @param {Object} context the |this| value to be used.
- @param {arguments} [1..n] optional arguments that are
- prepended to returned function's call.
- @return {Function} a function that uses |context| as the this
- value for the returned function and arguments 1..n for values
- of parameter variables to this function. */ Function.prototype.bind = function(context) { var fn = this, ap, concat, args, isPartial = arguments.length > 1; // Strategy 1: just bind, not a partialApply if(!isPartial) { return function() { if(arguments.length !== 0) { return fn.apply(context, arguments); } else { return fn.call(context); // faster in Firefox. } }; } else { // Strategy 2: partialApply ap = Array.prototype, args = ap.slice.call(arguments, 1); concat = ap.concat; return function() { return fn.apply(context, arguments.length === 0 ? args : concat.apply(args, arguments)); }; } };
There is really no point in exposing the two different cases in a spec. A spec should describe the semantics, not the implementation.
On Thu, Sep 18, 2008 at 11:49 PM, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:
On Thu, Sep 18, 2008 at 10:41, Garrett Smith <dhtmlkitchen at gmail.com> wrote:
2008/9/18 Mark S. Miller <erights at google.com>:
There is really no point in exposing the two different cases in a spec. A spec should describe the semantics, not the implementation.
OK.
Another version:
Function.prototype.bind( context, [ preArg1 [, preArg1 [,...]]])
- if this object does not have a [[Call]] property, throw a TypeError
- let FUN = this
- let PREARGS = a copy of the argument list provided by the caller, starting from position 1.
- Call [[Get]] method of PREARGS with argument "length".
- let RETFUN = a new Function object.
- let POSTARGS = a copy of FUN's argument list, starting from position Result(4).
- Set RETFUN's FormalParameterList to POSTARGS.
- let SCOPE = a new Object.
- For each property in PREARGS, create a Property on SCOPE with a corresponding name and value.
- Set RETFUN's scope chain to SCOPE
- Set the Base object for RETFUN to context.
- Return RETFUN.
The length property of the bind method is 1.
Shorter. A little easier to read in that it has no branch.
What about step 11? 11. Set the Base object for RETFUN to context.
The problem I see with that is that given:
var n = f.bind(o); n.call(p);
n.call will set the execution context to p. That seems wrong. n should always execute in context of o. Shouldn't it?
Interesting Side effect: The returned function's length is FUN.length
- PREARGS.length;
Thoughts?
Garrett
On Fri, Sep 19, 2008 at 1:16 AM, Garrett Smith <dhtmlkitchen at gmail.com> wrote:
On Thu, Sep 18, 2008 at 11:49 PM, Erik Arvidsson <erik.arvidsson at gmail.com> wrote:
On Thu, Sep 18, 2008 at 10:41, Garrett Smith <dhtmlkitchen at gmail.com> wrote:
2008/9/18 Mark S. Miller <erights at google.com>:
There is really no point in exposing the two different cases in a spec. A spec should describe the semantics, not the implementation.
OK.
Another version:
Function.prototype.bind( context, [ preArg1 [, preArg1 [,...]]])
- if this object does not have a [[Call]] property, throw a TypeError
- let FUN = this
- let PREARGS = a copy of the argument list provided by the caller, starting from position 1.
- Call [[Get]] method of PREARGS with argument "length".
- let RETFUN = a new Function object.
- let POSTARGS = a copy of FUN's argument list, starting from position Result(4).
- Set RETFUN's FormalParameterList to POSTARGS.
- let SCOPE = a new Object.
- For each property in PREARGS, create a Property on SCOPE with a corresponding name and value.
- Set RETFUN's scope chain to SCOPE
- Set the Base object for RETFUN to context. <ins>
- set RETFUN's FunctionBody to an equivalent of FUN's FunctionBody.
- Return RETFUN. </ins>
Mark S. Miller wrote: <snip>
So what we need now is a spec for Function.prototype.bind() in the peculiar pseudo-code style -- combining the worst of COBOL and assembly language -- used in the rest of the spec. Unfortunately, I won't have time between now and then. Would anyone care to contribute some text? Please?
Something like:
Function.prototype.bind (thisArg [, arg1 [,arg2... ]])
The bind method takes one or more arguments, thisArg and (optionally) arg1, arg2 etc, and returns a new function object.
The following steps are taken:
- Let T be ToObject(thisArg).
- Let F be the this object.
- If IsCallable(F) is false, throw a TypeError exception.
- Let A be a new (possibly empty) internal list of all of the argument values provided after thisArg (arg1, arg2 etc), in order.
- Create a new function object that has no [[Construct]] method, a [[Call]] method that, when executed, performs the "Algorithm for the [[Call]] methods of functions returned from the bind method" and has the values T, F and A internally associated with it.
- Return Result(5).
Algorithm for the [[Call]] methods of functions returned from the bind method.
When executed with zero or more arguments a function returned by the original Function.prototype.bind method uses the values of T, F and A that were associated with it at its creation and the following steps are taken:
- Let Args be a new internal list containing the same values as the list A in the same order.
- Add all of the values in the arguments list provided to the call to this function to the end of the list Args, in the order they appear in that arguments list (see 11.2.4).
- Invoke the [[Call]] method of F providing T as the this value and providing Args as the arguments values.
- Return Result(3).
In step 1 of the fist algorithm the call to ToObject will result in a TypeError if thisArg is null or undefined. There seems no point in explicitly checking for null or undefined values unless the intention is to default the - this - value to the global object, which would probably not be a good idea in a new method even if - call - and - apply - do that. However, this will be inconsistent with current JS library bind methods as because they use - call - or - apply - they would default the - this - value to the global object (though calling existing bind functions with null or undefined as the intended - this - value is likely to be indicative of a mistake having been made rather than a deliberate action).
Precedents for letting a call to ToObject throw a TypeError instead of checking and explicitly throwing a TypeError if a value is null or undefined can be found in, for example, the algorithm for Property Accessor (11.2.1).
Using ToObject will result in other primitive values being converted into String, Number or Boolean objects, but that is consistent with - call - and - apply -, and as existing JS library versions must employ - call - or - apply - it must then be consistent with their expected behaviour.
In step 5, although the association of the T, F and A values could be explicitly stated as being through the [[Scope]] of the new function the required behaviour does not depend on the method used to achieve the association, and as the [[Scope]] of the returned function cannot be observed there seems little point in saying anything about what value it may have.
Step 5 also requires that no [[Construct]] method be provided for the returned function. This will result in a TypeError being thrown if the function is used with the - new - operator. That seems like a good idea, as it makes no sense to use the - new - operator with the functions returned from Function.prototype.bind.
In the second algorithm the wording of step 3, the invoking of the [[Call]] method of the original - this - function, is based on step 7 in the [[Construct]] algorithm (13.2.2) and step 8 in the function call algorithm (11.2.3). This wording avoids any questions of what would happen if the original - this - function's "call" or "apply" properties were modified at runtime (or the "call" property of Function,prototype.apply or the "apply" property of Function.prototype call).
If you are trying to get the ES 3.1 spec into a more workable state it must be worth looking at the references to "SourceElement : FunctionDeclaration" in section 14 as currently "FunctionDeclaration" has been deleted from "SourceElement" and moved under "Statement". As of the 1st September draft there are no equivalent statements about the handling of "Statement : FunctionDeclaration" (would they be in section 12 or 14?). (And why retain "SourceElement" at all, as all SourceElements are now Statements?)
Richard Cornford.
I propose some modifications.
Function.prototype.bind (thisArg [, arg1 [,arg2... ]])
The bind method takes one or more arguments, thisArg and (optionally) arg1, arg2 etc, and returns a new function object.
The following steps are taken:
- Let T be thisArg.
- Let F be the this object.
- If IsCallable(F) is false, throw a TypeError exception.
- Let A be a new (possibly empty) internal list of all of the argument values provided after thisArg (arg1, arg2 etc), in order.
- Create a new function object that has no [[Construct]] method, no prototype property, a [[Call]] method that, when executed, performs the "Algorithm for the [[Call]] methods of functions returned from the bind method" and has the values T, F and A internally associated with it.
- Call the [[Get]] method of F with the argument "length".
- Call ToUint32(Result(6)).
- Let N be the size of A.
- Call the [[Put]] method of Result(5) with arguments "length" and max(Result(7) - N, 0).
- Call the [[Put]] method of Result(5) with arguments "name" and "".
- Call the [[Get]] method of F with the argument "parameters".
- Let P be Result(11).
- If P is not an Array, let P be a new array created as if by the expression new Array() where Array is the standard built-in constructor with that name.
- Call the [[Put]] method of Result(5) with arguments "parameters" and P.slice(N) where slice is the standard built-in method described in 15.4.4.10.
- Return Result(5).
Algorithm for the [[Call]] methods of functions returned from the bind method.
When executed with zero or more arguments a function returned by the original Function.prototype.bind method uses the values of T, F and A that were associated with it at its creation and the following steps are taken:
- Let Args be a new internal list containing the same values as the list A in the same order.
- Add all of the values in the arguments list provided to the call to this function to the end of the list Args, in the order they appear in that arguments list (see 11.2.4).
- If T is null or undefined, use the this value provided by the caller; otherwise, call ToObject(T).
- Invoke the [[Call]] method of F providing Result(3) as the this value and providing Args as the arguments values.
- Return Result(4).
The changes are:
-
The prototype property isn't needed as well as the [[Construct]] method.
-
Set appropriate values for length, name, and parameters properties.
-
The bound function should bind only arguments if thisArg is null or undefined. This allows us to create partially applied methods such as:
Array.prototype.sortAsNumber = Array.prototype.sort.bind(null, function (a, b) { return a - b; });
I might change my mind when I see it, but this sounds really scary. There are all kinds of places where dependencies on identifier names, global objects not having changed, etc. can sneak in.
Waldemar
The Redmond mtg is fast approaching. We'd like to put out an official for-Redmond-mtg draft of the ES3.1 spec by then. I had volunteered to write the spec for Function.prototype.bind(). Long term, I think we all agree we'd like to see this spec and many others self-hosted in EcmaScript. However, the discussion of self-hosting issues makes clear that this ain't gonna happen in time for ES3.1.
So what we need now is a spec for Function.prototype.bind() in the peculiar pseudo-code style -- combining the worst of COBOL and assembly language -- used in the rest of the spec. Unfortunately, I won't have time between now and then. Would anyone care to contribute some text? Please?