Function.prototype.bind
On Sep 9, 2008, at 9:47 PM, David-Sarah Hopwood wrote:
As an alternative to saying "the original bindings of..." in the spec, we could provide a way to actually get the original bindings in
ECMAScript code. This is independently useful, e.g. for secure subset run-times.
Have you read doku.php? id=strawman:lexical_scope yet?
The Harmony reference implementation work is very likely to cut back
the old ES4 RI to ES3 and go forward from there. To avoid hijacking
built-in behavior it needs something like the "use lexical scope"
pragma.
I have also assumed the existence of a "static" Function.apply which takes the function to be applied as an explicit first argument.
This was part of ES4 and it's in JS1.7+ in Firefox.
On Tue, Sep 9, 2008 at 9:51 PM, Brendan Eich <brendan at mozilla.org> wrote:
I have also assumed the existence of a "static" Function.apply which takes the function to be applied as an explicit first argument.
This was part of ES4 and it's in JS1.7+ in Firefox.
Since the function constructor is itself a kind of function, this overloading breaks Liskov substitutability. Suppose someone defines a logCall function:
function logCall(thisVal, meth, args) { log('applying ' + meth + ' to ' + args + ' with ' + thisVal); return func.apply(thisVal, args); }
doing a logged call to the Function constructor
logCall(Function, null, ['foo', 'bar', 'foo + bar'])
will do the wrong thing for no good reason.
On Tue, Sep 9, 2008 at 11:09 PM, Mark S. Miller <erights at google.com> wrote:
function logCall(thisVal, meth, args) { log('applying ' + meth + ' to ' + args + ' with ' + thisVal); return func.apply(thisVal, args); }
Oops. I meant
function logCall(thisVal, meth, args) { log('applying ' + meth + ' to ' + args + ' with ' + thisVal); return meth.apply(thisVal, args); }
On Sep 9, 2008, at 11:09 PM, Mark S. Miller wrote:
On Tue, Sep 9, 2008 at 9:51 PM, Brendan Eich <brendan at mozilla.org>
wrote:I have also assumed the existence of a "static" Function.apply which takes the function to be applied as an explicit first argument.
This was part of ES4 and it's in JS1.7+ in Firefox.
Since the function constructor is itself a kind of function, this overloading breaks Liskov substitutability.
LSP is not preserved by many cases in OO languages. Is it important
here? If so, could you suggest a fix (a new name for Function.apply
is ducking the issue; Object.apply seems misplaced). Thanks,
On Tue, Sep 9, 2008 at 11:29 PM, Brendan Eich <brendan at mozilla.org> wrote:
On Sep 9, 2008, at 11:09 PM, Mark S. Miller wrote:
On Tue, Sep 9, 2008 at 9:51 PM, Brendan Eich <brendan at mozilla.org> wrote:
I have also assumed the existence of a "static" Function.apply which takes the function to be applied as an explicit first argument.
This was part of ES4 and it's in JS1.7+ in Firefox.
Since the function constructor is itself a kind of function, this overloading breaks Liskov substitutability.
LSP is not preserved by many cases in OO languages. Is it important here?
I think it's a valuable property and should not be broken without a compelling reason.
If so, could you suggest a fix (a new name for Function.apply is ducking the issue;
Why ducking? If this operation is needed, I think a different name is exactly the right solution. JavaScript does dispatch based on property name, so its method dispatch does simple name-based polymorphism. E does method dispatch by name and arity. Java by name and a too-complex algorithm for method selection based on argument types. In these other cases, the extra information can be modeled as mangled into the name that's dispatched on (and it is often implemented this way as well). Whatever kind of name gets dispatched on, a derived member should not override the base member of the same name without honoring the base's contract.
Object.apply seems misplaced). Thanks,
The object constructor is also a kind of function, so that would have the same problem.
On Tue, Sep 9, 2008 at 11:51 PM, Mark S. Miller <erights at google.com> wrote:
On Tue, Sep 9, 2008 at 11:29 PM, Brendan Eich <brendan at mozilla.org> wrote:
On Sep 9, 2008, at 11:09 PM, Mark S. Miller wrote:
On Tue, Sep 9, 2008 at 9:51 PM, Brendan Eich <brendan at mozilla.org> wrote:
I have also assumed the existence of a "static" Function.apply which takes the function to be applied as an explicit first argument.
This was part of ES4 and it's in JS1.7+ in Firefox.
So Function.apply is the same as Function.prototype.apply, unless you set the javascript version to 1.7?
Since the function constructor is itself a kind of function, this overloading breaks Liskov substitutability.
Hmm. Yeah that would be kind of contradictory, wouldn't it? Function.apply cannot be two things at once. Yeah, stuff like that is annoying. I suppose scripts could feature detect that by checking Function.apply.length (provided the new generic Function.apply's length is 3, not 2).
If so, could you suggest a fix (a new name for Function.apply is ducking the issue;
Why ducking? If this operation is needed,
The benefit is that it might be a little shorter, I guess.
I think a different name is exactly the right solution.
Function.applyCall?
if(!Function.applyCall) Function.applyCall = function(callable, context, arr) { return Function.prototype.apply.call(fun, context, arr); };
(arr is an object like arguments, an Array object, a new type of collection).
It would be useful to know what implements [[Call]] that can be passed to that. For example:
if(Function.isCallable( o )) { Function.callCall( o, ctx, "42" ); }
(callCall = Function.callCall = function(callable, context) { return fun.apply(context, [].slice.call(arguments,2)); });
Garrett
On Wed, Sep 10, 2008 at 12:22 AM, Garrett Smith <dhtmlkitchen at gmail.com> wrote:
On Tue, Sep 9, 2008 at 11:51 PM, Mark S. Miller <erights at google.com> wrote:
On Tue, Sep 9, 2008 at 11:29 PM, Brendan Eich <brendan at mozilla.org> wrote:
On Sep 9, 2008, at 11:09 PM, Mark S. Miller wrote:
On Tue, Sep 9, 2008 at 9:51 PM, Brendan Eich <brendan at mozilla.org> wrote:
(callCall = Function.callCall = function(callable, context) { return fun.apply(context, [].slice.call(arguments,2)); });
Sorry, that should be:
Function.callCall = function(callable, context) { return callable.apply(context, [].slice.call(arguments,2)); };
Garrett
On Sep 9, 2008, at 11:51 PM, Mark S. Miller wrote:
LSP is not preserved by many cases in OO languages. Is it
important here?I think it's a valuable property
It's come up before, but is it a sacred cow? Method overriding in
classical OOP breaks it. Undeclared exceptions are another hard case.
Not that we're ready for inheritance with overriding in any "classes
as sugar" proposal (yet!), and we should never add declared
exceptions (please! ;-)).
I'm willing to rename Function.apply, but let's talk some more about
the better name, and about why it matters.
Why ducking? If this operation is needed,
Do you think it's not needed?
Some method of applying callable objects that aren't functions, where
the new method does not depend on Function.prototype.apply, is
necessary in our experience. You could try to stick to
Function.prototype.apply and add integrity by providing a frozen
standard library object with Function in it, but writing
Function.prototype.apply.call(callable, thisobj, argsarray) is
painful, and frozen standard library object is bloat we may not want
in any case, certainly not just to solve the problem David-Sarah cites.
There are other ways to provide a frozen apply-any-callable function,
but the point is that something is needed beyond the replaceable
Function.prototype.apply slot.
I think a different name is exactly the right solution.
Could you suggest a new name?
LSP means none of the static generics proposed for Function should
have the same name as their Function.prototype counterparts (apply,
bind, call -- abc). We could just mangle them all with some prefix,
Garrett just suggested "call": callApply, callBind, callCall. Ugly,
arguably less usable in practice than the LSP violation you point out
(I'm skeptical that Function would ever by .apply'ed indirectly), but
doable. Better name ideas welcome.
I'm to blame for this LSP violation, but in my defense I'll say that
it was not intentional. I don't think of Function as a function,
rather as a constructor. Constructors can't be .apply'ed when invoked
via operator new -- a bug that the proposed and so-far warmly-
received spread operator solves nicely:
new callable(...argsarray)
Note no explicit |this| parameter for constructors, so spread does
not solve the Function.prototype.apply problem for plain calls:
Safe.universalApply(callable, thisobj, argsarray)
can't be rewritten as:
callable(this=thisobj, ...argsarray)
as Tucker suggested recently (he used receiver=, but I still think
this assignment is better, since it's illegal currently and it avoids
adding a new contexually reserved word -- detail!).
Still, if we had spread, would we be bothering with Function.apply,
or with those pesky strict mode arguments tweaks? Food for thought.
Sometimes adding new syntax avoids silly name mangling and
unnecessary modalities.
On Sep 10, 2008, at 12:22 AM, Garrett Smith wrote:
This was part of ES4 and it's in JS1.7+ in Firefox.
So Function.apply is the same as Function.prototype.apply, unless you set the javascript version to 1.7?
No, I was dreaming. JS1.x has no Function.apply other than the one
you'd expect per ES3 -- the one inherited from Function.prototype.apply.
We only talked about Function.apply for ES4 (here). I misremembered.
Whew!
So the LSP violation was on paper only. No substitutions were harmed
making this movie. Back to the name-mangling show. ;-)
Brendan Eich wrote:
On Sep 9, 2008, at 9:47 PM, David-Sarah Hopwood wrote:
As an alternative to saying "the original bindings of..." in the spec, we could provide a way to actually get the original bindings in ECMAScript code. This is independently useful, e.g. for secure subset run-times.
Have you read strawman:lexical_scope yet?
I had seen a previous similar proposal, but I don't think it included the fixed bindings for the primordials (Array etc.)
However, unlike what I proposed, 'strawman:lexical_scope' doesn't make the bindings of the primordials that are visible when it is active deeply immutable. I suppose that's difficult if you want the prototypes to appear to be the same between code that is in such a block and code that is not -- for instance, '"" instanceof String' should work whether or not 'String' refers to a deeply immutable or mutable constructor.
The Harmony reference implementation work is very likely to cut back the old ES4 RI to ES3 and go forward from there. To avoid hijacking built-in behavior it needs something like the "use lexical scope" pragma.
So, is this a valid self-hosting specification of bind?
(function() { use lexical scope;
Function.prototype.bind = function(self, var_args) {
const thisFunc = this;
const leftArgs = Array.slice(arguments, 1);
return function(var_args) {
const args = leftArgs.concat(Array.slice(arguments, 0));
return Function.apply(thisFunc, self, args);
};
};
)();
Note that this isn't useful for ES3.1 unless 'use lexical scope' is added to ES3.1 (or implied by ES3.1-strict).
I have also assumed the existence of a "static" Function.apply which takes the function to be applied as an explicit first argument.
This was part of ES4 and it's in JS1.7+ in Firefox.
Good. I think it should be in ES3.1.
On Wed, Sep 10, 2008 at 12:49 AM, Brendan Eich <brendan at mozilla.org> wrote:
On Sep 9, 2008, at 11:51 PM, Mark S. Miller wrote:
LSP is not preserved by many cases in OO languages. Is it important here?
I think it's a valuable property
It's come up before, but is it a sacred cow?
Mu?
Method overriding in classical OOP breaks it.
How? I don't understand.
Undeclared exceptions are another hard case.
How?
Not that we're ready for inheritance with overriding in any "classes as sugar" proposal (yet!), and we should never add declared exceptions (please! ;-)).
Yup.
I'm willing to rename Function.apply, but let's talk some more about the better name, and about why it matters.
Why ducking? If this operation is needed,
Do you think it's not needed?
I do think it's not needed.
Some method of applying callable objects that aren't functions, where the new method does not depend on Function.prototype.apply, is necessary in our experience. You could try to stick to Function.prototype.apply and add integrity by providing a frozen standard library object with Function in it, but writing Function.prototype.apply.call(callable, thisobj, argsarray) is painful, and frozen standard library object is bloat we may not want in any case, certainly not just to solve the problem David-Sarah cites.
Rare cases that can be provided by the rare library that needs them. No reason to burden the API visible to all programmers.
There are other ways to provide a frozen apply-any-callable function, but the point is that something is needed beyond the replaceable Function.prototype.apply slot.
I think a different name is exactly the right solution.
Could you suggest a new name?
LSP means none of the static generics proposed for Function should have the same name as their Function.prototype counterparts (apply, bind, call -- abc). We could just mangle them all with some prefix, Garrett just suggested "call": callApply, callBind, callCall. Ugly, arguably less usable in practice than the LSP violation you point out (I'm skeptical that Function would ever by .apply'ed indirectly), but doable. Better name ideas welcome.
I like the idea of a general naming scheme for relating the instance method to its static-generic equivalent. I have occasionally use an "Of" suffix for this in some of my own code. YMMV.
Still, if we had spread, would we be bothering with Function.apply, or with those pesky strict mode arguments tweaks? Food for thought. Sometimes adding new syntax avoids silly name mangling and unnecessary modalities.
Is "spread" the same as "splat"?
Assuming they are, I agree. Unfortunately, we won't have splat in ES3.1, so we won't be able to simply kill 'arguments' in ES-H-strict. Hence the unfortunate need for tweaking it. But any static generic Function apply will be post ES3.1 anyway, and so discussion of it should assume the existence of splat.
On Sep 10, 2008, at 10:15 AM, David-Sarah Hopwood wrote:
However, unlike what I proposed, 'strawman:lexical_scope' doesn't make the bindings of the primordials that are visible when it is active deeply immutable. I suppose that's difficult if you want the prototypes to appear to be the same between code that is in such a block and code that is not -- for instance, '"" instanceof String' should work whether or not 'String' refers to a deeply immutable or mutable constructor.
Right. Also, people mutate standard objects, whether it's a good idea
in the large or not. The "use lexical scope" idea is not meant to
break compatibility on that point.
It's rarer (although still done -- see ) for web JS to mutate the
global bindings for Object, Date, etc.
The Harmony reference implementation work is very likely to cut
back the old ES4 RI to ES3 and go forward from there. To avoid hijacking
built-in behavior it needs something like the "use lexical scope" pragma.So, is this a valid self-hosting specification of bind?
(function() { use lexical scope;
Function.prototype.bind = function(self, var_args) { const thisFunc = this; const leftArgs = Array.slice(arguments, 1); return function(var_args) { const args = leftArgs.concat(Array.slice(arguments, 0)); return Function.apply(thisFunc, self, args); }; };
)();
Note that this isn't useful for ES3.1 unless 'use lexical scope' is added to ES3.1 (or implied by ES3.1-strict).
It's close (saw your followup) but:
-
It does not make Function.prototype.bind non-enumerable.
-
It's vulnerable to mutations of Array.slice,
Array.prototype.concat, and Function.apply.
1 is fixable using ES3.1's Object.defineProperty in the self-hosted
reference implementation.
2 is a wider problem, which could be addressed in ES3.1 by freezing
certain standard constructors.
But that's again woefully incompatbile: you can't freeze the self-
hosted Array and have it be used as real code does today, by
instanceof tests (never mind mutation revealing disparate identity).
The ES4 RI used the intrinsic namespace, and other namespaces, to
access implementation helpers that underlay the prototype and any
(mutable) static methods. Without namespaces, one could use private
names of some kind (simply via lexical scope, I think), to access
such helpers in an ES3-ish RI. With private names of some sort (the
Name object idea) one could hide the helpers too.
I'm working to get the ES3+ RI restarted. It has as a goal avoiding
too much magic in order to self-host the built-ins. "use lexical
scope" and Object.defineProperty may be enough to self-host without
being vulnerable to "user-code overrides".
I have also assumed the existence of a "static" Function.apply which takes the function to be applied as an explicit first argument.
This was part of ES4 and it's in JS1.7+ in Firefox.
Good. I think it should be in ES3.1.
See my followup -- late night dream (or nightmare) on my part. JS1.7
did add static generic Array.slice(arraylike, start, end) and so on
(String.slice, etc.), but it did not add any such Function.apply/bind/
call. LSP is safe!
Could you suggest a new name for Function.apply (and the static bind
and call, for that matter)? Thanks,
On Tue, Sep 9, 2008 at 9:47 PM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:
Mark S. Miller wrote:
On Tue, Sep 9, 2008 at 9:21 AM, Mark S. Miller <erights at google.com> wrote:
I still need to spec Function.prototype.bind in our funny spec language. Anyone care to contribute a draft?
I have also assumed the existence of a "static" Function.apply which takes the function to be applied as an explicit first argument. This is necessary because otherwise there is no way to bind 'this' correctly given that thisFunc.apply may have been overridden (or at least, I don't know of any way to do it).
That code cannot be tested it in any implementations that do not have your Function.apply. if(!Function.apply) { ... }
Just for fun...
You'll have to wait a few 10 seconds to see any results. The returned function is faster when it does bind only (GarrettsBind tests #3 and #4, no partialApply), or a partialApply called with 0 args (GarrettsBind test #1).
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "www.w3.org/TR/html4/loose.dtd"> <html lang="en"> <head> <meta http-equiv="content-type" content="text/html; charset=iso-8859-1"> <title>BindFunction.html</title> </head> <body> <script type="text/javascript"> /**
- @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 applies the original
- function with - context - as the thisArg. */
var bindFunctions = [ { name : "GarrettsBind", fun:function GarrettsBind(context){ var fn = this, slice = Array.prototype.slice, args, isPartial = arguments.length > 1; if(isPartial) args = slice.call(arguments, 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
return function() {
return fn.apply(context,
arguments.length === 0 ? args :
args.concat(slice.call(arguments)));
};
}
}
}, { name : "MarksBind", fun : function MarksBind(self, var_args) { var thisFunc = this; var slice = Array.prototype.slice; var leftArgs = slice.call(arguments, 1); return function(var_args) { var args = leftArgs.concat(slice.call(arguments, 0)); return thisFunc.apply(self, args); }; } } ]; var currBindFunction = 0;
// Mock Data function x(a,b,c) { return ((a||0) + (b||0) + (c||0) + this.v); } var o = {v: 1000}; var value; var ITER = 10000;
var d = new Date, result, resultsDiv, currentTest = 0, results = [bindFunctions[0].name];
function oncomplete() { if(++currentTest === tests.length) { if(++currBindFunction === bindFunctions.length) { resultsDiv = document.getElementById('resultsDiv'); resultsDiv.innerHTML = results.join('\n---------------------\n'); return; } else { var bo = bindFunctions[currBindFunction]; currentTest=0; results.push('\n', bo.name); Function.prototype.bind = bo.fun; } } setTimeout(function(){tests[currentTest].run();}, 500); }
var tests = [ { run : (function testBindPartialNoArgs() { d=new Date; var adder = x.bind(o, 1, 10, 100); for(var i = 0; i < ITER; i++) { result = adder(); } results.push( new Result("x.bind(o, 1, 10, 100); adder()", result===1111, new Date-d) ); oncomplete(); }) },
{ run : (function testBindPartialWithArgs() { d=new Date; var adder = x.bind(o, 1); for(var i = 0; i < ITER; i++) { result = adder(10, 100); } results.push( new Result("x.bind(o, 1); adder(10, 100)", result===1111, new Date-d) ); oncomplete(); }) },
{ run : (function testBindWithArgs() { d=new Date; var adder = x.bind(o); for(var i = 0; i < ITER; i++) { result = adder(1, 10, 100); } results.push( new Result("x.bind(o); adder(1, 10, 100)", result===1111, new Date-d) ); oncomplete(); }) }, { run : (function testBindNoArgs() { d=new Date; var adder = x.bind(o); for(var i = 0; i < ITER; i++) { result = adder(); } results.push( new Result("x.bind(o); adder()", result===1000, new Date-d) ); oncomplete(); }) } ];
Function.prototype.bind = bindFunctions[0].fun; tests[0].run(); tests function Result(s, pass, time) { this.s = s; this.result = (pass === true ? "PASS" : "FAIL"); this.time = time; } Result.prototype.toString = function() { return this.s + "\nresult: " + this.result + "\ntime: " + this.time; }
</script> <pre id="resultsDiv"></pre> </body> </html>
(To be able to test in other browsers, Mark's bind was modified so that it does not use Array.slice, but instead Array.prototype.slice.call)
Garrett
On Sep 10, 2008, at 11:03 AM, Brendan Eich wrote:
It's rarer (although still done -- see ) for web JS to mutate the global bindings for Object, Date, etc.
That "see" in the parenthetical was meant to link here:
On Sep 10, 2008, at 10:24 AM, Mark S. Miller wrote:
Method overriding in classical OOP breaks it.
How? I don't understand.
It depends on the language, but your subtype method override can
flagrantly violate my supertype method's "contract". Exceptions, non-
termination, lots of stuff not enforced by the type system. And JS's
dynamic type system doesn't say much about what some "apply"-named
method might do.
But I didn't mean to digress on this point -- you're right that
Function.apply should delegate to Function.prototype.apply.
Rare cases that can be provided by the rare library that needs them. No reason to burden the API visible to all programmers.
The burden is entirely on the rare (or not so rare -- every Ajax
library I know of) case.
There is no burden, with the right name, in Function.genericApply (or
whatever it should be called). It's not on any prototype chain.
Pedagogical approaches to teaching JS can reveal it later, when
teaching the ninja secrets. Calling it a burden is like saying my
car's automatic transmission having a fifth gear is a burden.
Is "spread" the same as "splat"?
Yes -- spread was (Tucker, please speak up if I'm misremembering
again) the canonical name in Dylan or CL. Splat may be a Pythonism,
and sounds rude. For the spec's dignity we were inclined toward spread.
Assuming they are, I agree. Unfortunately, we won't have splat in ES3.1, so we won't be able to simply kill 'arguments' in ES-H-strict. Hence the unfortunate need for tweaking it. But any static generic Function apply will be post ES3.1 anyway, and so discussion of it should assume the existence of splat.
Sounds good (so long as arguments doesn't become a full blown Array).
Mark S. Miller wrote:
On Tue, Sep 9, 2008 at 9:51 PM, Brendan Eich <brendan at mozilla.org> wrote:
I have also assumed the existence of a "static" Function.apply which takes the function to be applied as an explicit first argument. This was part of ES4 and it's in JS1.7+ in Firefox.
Since the function constructor is itself a kind of function, this overloading breaks Liskov substitutability. Suppose someone defines a logCall function:
[corrected version]
function logCall(thisVal, meth, args) { log('applying ' + meth + ' to ' + args + ' with ' + thisVal); return meth.apply(thisVal, args); }
doing a logged call to the Function constructor
logCall(Function, null, ['foo', 'bar', 'foo + bar'])
will do the wrong thing for no good reason.
This can be fixed by either renaming Function.apply, or conditioning on whether there are two or three arguments (the former is more elegant). The important point is the need for a static 3-argument version of 'apply', not what it is called. I will suggest the name 'apply3' as a starting point (for symmetry and convenience 'call3' should also be provided).
Note that apply3 and call3 are not, AFAICS, reliably expressible in terms of Function.prototype.apply and Function.prototype.call.
On Wed, Sep 10, 2008 at 3:41 PM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:
Mark S. Miller wrote:
On Tue, Sep 9, 2008 at 9:51 PM, Brendan Eich <brendan at mozilla.org> wrote:
This can be fixed by either renaming Function.apply, or conditioning on whether there are two or three arguments (the former is more elegant). The important point is the need for a static 3-argument version of 'apply', not what it is called. I will suggest the name 'apply3' as a starting point (for symmetry and convenience 'call3' should also be provided).
Why would "Function.apply3" need three arguments?
Function.prototype.apply doesn't actually need any arguments.
See for yourself:-
function fun(){ alert(42); } Function.prototype.apply.call(fun);
Brendan Eich wrote:
On Sep 10, 2008, at 10:15 AM, David-Sarah Hopwood wrote:
However, unlike what I proposed, 'strawman:lexical_scope' doesn't make the bindings of the primordials that are visible when it is active deeply immutable. I suppose that's difficult if you want the prototypes to appear to be the same between code that is in such a block and code that is not -- for instance, '"" instanceof String' should work whether or not 'String' refers to a deeply immutable or mutable constructor.
Right. Also, people mutate standard objects, whether it's a good idea in the large or not. The "use lexical scope" idea is not meant to break compatibility on that point.
It's rarer (although still done -- see [bugzilla.mozilla.org/show_bug.cgi?id=409252#c4]) for web JS to mutate the global bindings for Object, Date, etc.
ECMA-262 is entirely unclear about what effects that is supposed to have, and it did different things between implementations (for example FF2 used shallow binding of at least Array, while IE7 mostly ignored changes to these bindings). So if there is any code relying on doing this, at best it worked by accident.
I see only references to rebinding of 'Date' in that bug report (and 'Namespace' which is not relevant to ES3.1). The semantics of rebinding Date are much clearer than they are for constructors that are implicitly accessed as a result of evaluating literals (Array, Function, Object, String and RegExp). In particular, there are intractable bootstrapping issues in specifying what it should mean to rebind 'Object' or 'Function', at least. Furthermore, allowing rebinding of these implicitly accessed constructors could inhibit useful optimizations, while I assume no-one really cares about optimizing Date.
So here is my strawman proposal:
- leave 'Date' Writable in the global scope;
- make Array, Function, Object, String and RegExp non-Writable and non-Configurable, unconditionally;
- make Array, Function, Object, String and RegExp not declarable as variable names;
- make all of the new "static" methods on Array, Function, Object and String non-Writable and non-Configurable, unconditionally.
This will not break Ajax library code that tries to install compatible implementations of these new methods, because:
- if that code is running in non-strict mode, the attempted updates will be silently ignored.
- if it is running in strict mode, then it must be new code that knows to catch the resulting exception.
Note that the Array, Function, Object, String and RegExp constructors would not be sealed, and the prototypes would still be mutable. So this proposal is not attempting to address the secure sublanguage issues (at least not on its own), only the issue with self-hosting.
With this design, it's no longer necessary to worry about rebinding or shadowing in the self-hosting specifications, or in the expansions of new syntactic sugar, provided that they only use the static methods and do not use Date. I do not believe that anything important on the web will break as a result of these changes -- and if it does, then I think it desperately needed to be broken as soon as possible.
The self-hosting specification of Function.prototype.bind would be:
Object.defineProperty(Function.prototype, 'bind', { writable: false, enumerable: false, configurable: false, value: function(self, var_args) { const thisFunc = this; const leftArgs = Array.slice(arguments, 1); return function(var_args) { const args = Array.concat(leftArgs, Array.slice(arguments, 0)); return Function.staticApply(thisFunc, self, args); }; } });
(no 'use lexical scope' needed).
Could you suggest a new name for Function.apply (and the static bind and call, for that matter)? Thanks,
I suggested apply3 and call3 in a previous post, but on second thoughts I'd like to change that to staticApply, staticCall and staticBind. The '3' doesn't work for call or bind because they take variable arguments, and it's ugly even for apply.
Mark S. Miller wrote:
On Wed, Sep 10, 2008 at 12:49 AM, Brendan Eich <brendan at mozilla.org> wrote:
I'm willing to rename Function.apply, but let's talk some more about the better name, and about why it matters.
Actually now that I think about it, there is an easy fix:
Function.apply = <original Function.prototype.apply>;
Then use Function.apply.call(callable, thisobj, argsarray).
David-Sarah Hopwood wrote:
Mark S. Miller wrote:
On Wed, Sep 10, 2008 at 12:49 AM, Brendan Eich <brendan at mozilla.org> wrote:
I'm willing to rename Function.apply, but let's talk some more about the better name, and about why it matters.
Actually now that I think about it, there is an easy fix:
Function.apply = <original Function.prototype.apply>;
Then use Function.apply.call(callable, thisobj, argsarray).
I should have said that this is assuming the adoption of my other proposal in which static methods of Function (for example) are non-Writable and non-Configurable.
Garrett Smith wrote:
On Wed, Sep 10, 2008 at 3:41 PM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:
Mark S. Miller wrote:
On Tue, Sep 9, 2008 at 9:51 PM, Brendan Eich <brendan at mozilla.org> wrote:
This can be fixed by either renaming Function.apply, or conditioning on whether there are two or three arguments (the former is more elegant). The important point is the need for a static 3-argument version of 'apply', not what it is called. I will suggest the name 'apply3' as a starting point (for symmetry and convenience 'call3' should also be provided).
Why would "Function.apply3" need three arguments?
Because Function.prototype.apply takes two arguments, and apply3 (or staticApply, or whatever it is called) makes the 'this' argument explicit.
Function.prototype.apply doesn't actually need any arguments.
No, it always takes two arguments, which may be undefined. But since an undefined argument can't be distinguished from a missing one, that does mean it's not possible to condition on whether there are two or three arguments. (Besides, I withdrew the suggested name 'apply3'.)
See for yourself:-
function fun(){ alert(42); } Function.prototype.apply.call(fun);
This is equivalent to Function.prototype.apply.call(fun, undefined, undefined);
On Sep 10, 2008, at 5:37 PM, David-Sarah Hopwood wrote:
Brendan Eich wrote:
On Sep 10, 2008, at 10:15 AM, David-Sarah Hopwood wrote:
However, unlike what I proposed, 'strawman:lexical_scope' doesn't
make the bindings of the primordials that are visible when it is active deeply immutable. I suppose that's difficult if you want the prototypes to appear to be the same between code that is in such a block and code that is not -- for instance, '"" instanceof String' should work whether or not 'String' refers to a deeply immutable or mutable constructor.Right. Also, people mutate standard objects, whether it's a good
idea in the large or not. The "use lexical scope" idea is not meant to break compatibility on that point.It's rarer (although still done -- see [bugzilla.mozilla.org/show_bug.cgi?id=409252#c4]) for web JS to mutate the global bindings for Object, Date, etc.
ECMA-262 is entirely unclear about what effects that is supposed to
have,
Or where the language is clear, it is not consistent:
and it did different things between implementations (for example
FF2 used shallow binding of at least Array,
What do you mean by "shallow binding"?
while IE7 mostly ignored changes to these bindings). So if there is any code relying on doing this, at
best it worked by accident.
But it does work, and that's why we couldn't change it for Firefox 3.
Joel Spolsky would have our heads.
I see only references to rebinding of 'Date' in that bug report (and 'Namespace' which is not relevant to ES3.1).
Namespace is from ECMA-357, E4X. It's relevant because we do not want
to special-case the constructor binding rules per standard object. We
want one rule to bind them all, partly for simplicity of
implementation, mostly for cognitive simplicity for programmers using
JS, and anyway to avoid borrowing compatibility trouble (more below,
where you propose borrowing more than your net worth :-/).
You have not looked at all the regressions from the original bug:
bugzilla.mozilla.org/show_bug.cgi?id=376957
listed in the "Depends on:" row of links.
bugzilla.mozilla.org/show_bug.cgi?id=407323 (XML, ECMA-357).
bugzilla.mozilla.org/show_bug.cgi?id=407727 (let Object, same
as var Object)
bugzilla.mozilla.org/show_bug.cgi?id=407957 (Iterator,
similar to Namespace)
and the big one, bigger even than fogbuz (sorry, Joel):
bugzilla.mozilla.org/show_bug.cgi?id=412324 (Error)
An old MSN compat.js file also rebinds Error unconditionally. I'm not
sure it is used any longer, but it's still up:
stj.msn.com/br/gbl/js/3/mozcompat.js
The semantics of rebinding Date are much clearer than they are for constructors that are
implicitly accessed as a result of evaluating literals (Array, Function, Object, String and RegExp). In particular, there are intractable bootstrapping issues in specifying what it should mean to rebind 'Object' or
'Function', at least. Furthermore, allowing rebinding of these implicitly accessed constructors could inhibit useful optimizations, while I assume no-one really cares about optimizing Date.
Without literal syntax, you are right as far as optimization goes.
Integrity is another issue, I don't need to tell you!
Error and its subtypes have no literal syntax, but are constructed by
the runtime and thrown. ES3 says (e.g. 15.1.2.1 step 2) "throw a
SyntaxError exception", and this is taken to mean construct an
instance without reflecting on the current binding of SyntaxError,
rather use a memoized prototype and internal [[Class]], etc.
So here is my strawman proposal:
- leave 'Date' Writable in the global scope;
- make Array, Function, Object, String and RegExp non-Writable and non-Configurable, unconditionally;
No; bugzilla.mozilla.org/show_bug.cgi?id=412324 is just a
taste of what you'll get in the way of bug reports, I predict. I'll
bet real money on this.
- make Array, Function, Object, String and RegExp not declarable as variable names;
- make all of the new "static" methods on Array, Function, Object and String non-Writable and non-Configurable, unconditionally.
I think you are missing a few classes (e.g. Error).
Having such complicated rules also seems bad. There will be more
built-in classes. And what about "host constructors" like Image,
Option, HTMLDivElement, etc.? Why should they have different binding
rules?
This will not break Ajax library code that tries to install compatible implementations of these new methods, because:
- if that code is running in non-strict mode, the attempted updates will be silently ignored.
See bugzilla.mozilla.org/show_bug.cgi?id=412324 -- silent
failure to update a ReadOnly property still breaks things.
- if it is running in strict mode, then it must be new code that knows to catch the resulting exception.
Note that the Array, Function, Object, String and RegExp constructors would not be sealed, and the prototypes would still be mutable. So
this proposal is not attempting to address the secure sublanguage issues (at least not on its own), only the issue with self-hosting.
Self-hosting is a specification and potentially an implementation
detail, not a reason for incompatible change that we could not ship
in Firefox 3. Good luck getting any other browser, including IE9 (in
two years?) to do likewise.
With this design, it's no longer necessary to worry about rebinding or shadowing in the self-hosting specifications, or in the
expansions of new syntactic sugar, provided that they only use the static methods and do not use Date.
Sorry, you're mistaken. The self-hosted Date needs to refer to its
own original binding and properties. Etc.
I do not believe that anything important on the web
I don't know what to make of this. You're wrong, and I cited evidence
to the contrary. But feel free to start a new browser project, break
compatibility, grow your market share, and prove me wrong.
will break as a result of these changes -- and if it does, then I
think it desperately needed to be broken as soon as possible.
This is wrong in at least six dimensions. You are not god-emperor of
the web. The web is more like the boss of you. You must woo it, win
it over with better ways to do things currently done via bad old APIs
today, before you can remove those APIs and force your will on it.
Anyway, that's my free advice, and I've been at this game for a long
time (and we've won market share back from near 0 with Firefox -- but
not by breaking compatibility! On the contrary, we've had to add IE
compatibility over time, e.g. document.all -- the game theory here is
not hard to figure out).
The self-hosting specification of Function.prototype.bind would be:
Object.defineProperty(Function.prototype, 'bind', { writable: false, enumerable: false, configurable: false, value: function(self, var_args) { const thisFunc = this; const leftArgs = Array.slice(arguments, 1); return function(var_args) { const args = Array.concat(leftArgs, Array.slice(arguments,
0)); return Function.staticApply(thisFunc, self, args); }; } });(no 'use lexical scope' needed).
Let's see a Date method or three.
Could you suggest a new name for Function.apply (and the static
bind and call, for that matter)? Thanks,I suggested apply3 and call3 in a previous post, but on second thoughts I'd like to change that to staticApply, staticCall and staticBind. The '3' doesn't work for call or bind because they take variable arguments, and it's ugly even for apply.
Numbered methods look like versionitis; static is better but less
clear about meaning than generic (that these are "static methods",
i.e., function-valued properties of the constructor, is less
significant than that they work on all callables).
On Sep 10, 2008, at 6:21 PM, David-Sarah Hopwood wrote:
Mark S. Miller wrote:
On Wed, Sep 10, 2008 at 12:49 AM, Brendan Eich
<brendan at mozilla.org> wrote:I'm willing to rename Function.apply, but let's talk some more
about the better name, and about why it matters.Actually now that I think about it, there is an easy fix:
Function.apply = <original Function.prototype.apply>;
Then use Function.apply.call(callable, thisobj, argsarray).
Sure -- I've shown various apply.call formulations. The point is to
have a standard method not in the (a) mutably-bound and (b) long-
winded Function.prototype.apply property.
If we want to do nothing (for ES3.1 this seems like the right thing,
I agree with Mark), we can say "DIY" and paste your message above.
But you were in favor of adding something to ES3.1 for shorter-and-
more-secure callable apply, or so I thought.
Still thinking about spread and named-this-parameter superseding
apply in Harmony....
On Sep 10, 2008, at 6:24 PM, David-Sarah Hopwood wrote:
David-Sarah Hopwood wrote:
Mark S. Miller wrote:
On Wed, Sep 10, 2008 at 12:49 AM, Brendan Eich
<brendan at mozilla.org> wrote:I'm willing to rename Function.apply, but let's talk some more
about the better name, and about why it matters.Actually now that I think about it, there is an easy fix:
Function.apply = <original Function.prototype.apply>;
Then use Function.apply.call(callable, thisobj, argsarray).
I should have said that this is assuming the adoption of my other
proposal in which static methods of Function (for example) are non-Writable and non-Configurable.
Oh, I thought you were dodging the overwrite issue. But your proposal
is wildly incompatible and no browser implementor involved in TC39
will ship it (I predict, and aver on behalf of Mozilla, having tried
once for all standard constructors).
So where does that leave us?
Brendan Eich wrote:
On Sep 10, 2008, at 5:37 PM, David-Sarah Hopwood wrote:
Brendan Eich wrote:
On Sep 10, 2008, at 10:15 AM, David-Sarah Hopwood wrote:
However, unlike what I proposed, 'strawman:lexical_scope' doesn't make the bindings of the primordials that are visible when it is active deeply immutable. I suppose that's difficult if you want the prototypes to appear to be the same between code that is in such a block and code that is not -- for instance, '"" instanceof String' should work whether or not 'String' refers to a deeply immutable or mutable constructor.
Right. Also, people mutate standard objects, whether it's a good idea in the large or not. The "use lexical scope" idea is not meant to break compatibility on that point.
It's rarer (although still done -- see [bugzilla.mozilla.org/show_bug.cgi?id=409252#c4]) for web JS to mutate the global bindings for Object, Date, etc.
ECMA-262 is entirely unclear about what effects that is supposed to have,
Or where the language is clear, it is not consistent:
and it did different things between implementations (for example FF2 used shallow binding of at least Array,
What do you mean by "shallow binding"?
Shallow binding, as I'm using it here, is where a reference -- in this case the implicit reference to Array when an array literal is evaluated -- is resolved as if it were textually substituted. For example:
(function() { function Array() { alert('hi'); } []; })();
would alert on FF2, because the '[]' is interpreted as if it said 'new Array()', where Array is shallowly bound to the local function definition.
In FF3, Array is no longer shallowly bound, and so this code does not alert.
Having googled "shallow binding", I see that it also has other meanings (which probably are more canonical; for example Henry Baker's definition should take precedence), so I should have been more explicit.
while IE7 mostly ignored changes to these bindings). So if there is any code relying on doing this, at best it worked by accident.
But it does work, and that's why we couldn't change it for Firefox 3.
No, it does not work cross-browser. The above code doesn't alert on IE7, for instance. Also,
Array = function() { alert('hi'); }; [];
alerts on FF2 but not IE7.
Joel Spolsky would have our heads.
I see only references to rebinding of 'Date' in that bug report (and 'Namespace' which is not relevant to ES3.1).
Namespace is from ECMA-357, E4X. It's relevant because we do not want to special-case the constructor binding rules per standard object.
Well, that is exactly what my proposal does (and I explain why below). I'd appreciate it if you would address your criticism to what I actually proposed, rather than something I didn't.
There are exactly two categories of name in my proposal:
a) Array, Function, Object, String and RegExp b) Everything else.
There are several strong arguments for treating a) differently from b). To expand on them:
-
If you allow rebinding the names in a), you must specify precisely what it means to do so (otherwise allowing it is useless). There's a very significant specification complexity cost in doing that.
Note that if creating an object or a function runs the user-specified Object or Function constructor, then that constructor will not be able to do anything useful, because creating any object or function in it will cause an infinite regress.
If creating an object or function does not run the user-specified constructor, OTOH, then what was the point of rebinding it?
-
Existing JS implementations do not handle rebinding of the names in a) consistently (as demonstrated by the examples above). There is inconsistency between browsers, between browser versions, and between particular names in each version. Any code that rebinds these names works only by coincidence, and is liable to break at any time, regardless of whether it is nominally allowed by the spec.
-
Allowing these names to be rebound inhibits useful optimizations. For example, it inhibits the optimization that is assumed to be possible in the rationale document for the Object static methods. (Maybe this optimization can be recovered if it is done only within an 'if' block that tests 'Object === original_Object', and does not itself rebind 'Object'. But I think it is an unreasonable amount of implementation complexity to detect this case.)
-
The names in a) (and static methods on them) are those that we need to use to write self-hosting specifications, and that might be used in the expansions of syntactic sugar added in ES-Harmony. It is, I believe, completely infeasible to write any ECMAScript code that has predictable behaviour if these names cannot be relied on. (It's difficult enough if these are the only names that can be relied on, but it is possible, with care.)
-
Existing web content relies on rebinding the names in b), but not (or to a much lesser extent) the names in a).
We want one rule to bind them all,
That would be nice, but I don't believe that this consideration should override the arguments given above. ES3 is already rather haphazard in its assignment of attributes, particularly ReadOnly. (I'm not defending that, just pointing out that what we are starting from in how ES3 specifies attributes with is very far from "one rule to bind them all".)
partly for simplicity of implementation,
The implementation is trivial, using Object.defineProperty. So is the specification. We can add a footnote pointing out the irregularity, so that implementors can't miss it.
mostly for cognitive simplicity for programmers using JS,
Cognitive simplicity is thrown out of a 100th-storey window for any code that rebinds the names in a). Even if it were allowed, it would be necessary to avoid doing this if cognitive simplicity for programmers is a goal.
I agree that allowing names of built-in global constructors other than those in a) to be rebound, is slightly irregular. There's a simple solution to that: don't write code that rebinds other built-in global constructors either. Just because it is allowed (for compatibility), doesn't mean that it should be done. In fact we should probably explicitly deprecate it (which would potentially allow making all built-in global constructor names non-Writable and non-Configurable in some future version).
and anyway to avoid borrowing compatibility trouble (more below, where you propose borrowing more than your net worth :-/).
You have not looked at all the regressions from the original bug:
At the risk of repeating myself, let's be clear about which names I proposed to be non-Writable and non-Configurable:
Array, Function, Object, String and RegExp
Only those names. Not Date, XML, Namespace, Iterator, Error, Boolean, Number, Math, or anything else. So most of the compatibility issues described in these regressions are simply not relevant to what I proposed.
bugzilla.mozilla.org/show_bug.cgi?id=376957
listed in the "Depends on:" row of links.
bugzilla.mozilla.org/show_bug.cgi?id=407323 (XML, ECMA-357).
Not relevant because 'XML' is not in category a). As Jeff Walden says in comment #4 of that bug:
Rebinding XML doesn't do anything interesting -- unlike []/{} nothing
uses the XML binding. To me this makes it much less interesting as
something to lock down to make the language more predictable.
bugzilla.mozilla.org/show_bug.cgi?id=407727 (let Object, same as var Object)
I am perfectly happy to let this code break. Note that this report does not cite any evidence of web content that is actually using Object as a local; it is purely a theoretical incompatibility.
bugzilla.mozilla.org/show_bug.cgi?id=407957 (Iterator, similar to Namespace)
Iterator is not in category a).
and the big one, bigger even than fogbuz (sorry, Joel):
bugzilla.mozilla.org/show_bug.cgi?id=412324 (Error)
An old MSN compat.js file also rebinds Error unconditionally. I'm not sure it is used any longer, but it's still up:
Error is not in category a).
(But suppose for the sake of argument that it were. This code says "root.Error=function(){};", where 'root' refers to the global object. Since it is run in non-strict mode, the assignment to a non-Writable property will be silently ignored -- which is completely harmless in this case. In fact it is desirable, since the actual constructor for Error might do something important in a given implementation.
The same argument, of course, applies to assignments to Array, Function, Object, String and RegExp, where the code either happens to work, or was deliberately written to work if the assignment is silently ignored.)
The semantics of rebinding Date are much clearer than they are for constructors that are implicitly accessed as a result of evaluating literals (Array, Function, Object, String and RegExp). In particular, there are intractable bootstrapping issues in specifying what it should mean to rebind 'Object' or 'Function', at least. Furthermore, allowing rebinding of these implicitly accessed constructors could inhibit useful optimizations, while I assume no-one really cares about optimizing Date.
Without literal syntax, you are right as far as optimization goes. Integrity is another issue, I don't need to tell you!
Error and its subtypes have no literal syntax, but are constructed by the runtime and thrown. ES3 says (e.g. 15.1.2.1 step 2) "throw a SyntaxError exception", and this is taken to mean construct an instance without reflecting on the current binding of SyntaxError, rather use a memoized prototype and internal [[Class]], etc.
Yes, but that's easy to handle without making SyntaxError itself non-Writable/Configurable. Just define non-Writable/Configurable properties:
Object.Error Object.EvalError Object.RangeError Object.ReferenceError Object.SyntaxError Object.TypeError Object.URIError Object.Date Object.Number Object.Boolean Object.Math Object.XML Object.Iterator Object.Namespace
Now it is possible for any code that needs to refer to the original constructors, including the specification itself, to do so easily. For example 15.1.2.1 step 2 would just say "throw an Object.SyntaxError exception".
This is reasonable because there are only a relatively small number of places in the spec where Error objects are created (78, by my count). If we were to do the same thing for Array, Function, String and RegExp, then we'd have to make huge changes to the specification; it would be completely impractical.
Besides, making all of those changes would imply that it would not be useful to rebind Array, Function, String and RegExp, even if it were possible, because the rebinding would effectively be ignored (or worse, it would be used inconsistently, with some objects using the rebound prototypes and some not).
So here is my strawman proposal:
- leave 'Date' Writable in the global scope;
- make Array, Function, Object, String and RegExp non-Writable and non-Configurable, unconditionally;
No; bugzilla.mozilla.org/show_bug.cgi?id=412324 is just a taste of what you'll get in the way of bug reports, I predict.
That bug involves code that rebinds Error, which is not in category a).
I'll bet real money on this.
- make Array, Function, Object, String and RegExp not declarable as variable names;
- make all of the new "static" methods on Array, Function, Object and String non-Writable and non-Configurable, unconditionally.
I think you are missing a few classes (e.g. Error).
The omission of the Error constructors was quite deliberate. They're not created by literals, they're not important to optimize, and it is easy to work around their rebinding, as described above.
Having such complicated rules also seems bad. There will be more built-in classes. And what about "host constructors" like Image, Option, HTMLDivElement, etc.? Why should they have different binding rules?
They don't; they're like everything else in category b).
Having two categories of global name is not especially complicated. I also think you're underestimating the extent to which this approach can be used to simplify the specification.
This will not break Ajax library code that tries to install compatible implementations of these new methods, because:
- if that code is running in non-strict mode, the attempted updates will be silently ignored.
See bugzilla.mozilla.org/show_bug.cgi?id=412324 -- silent failure to update a ReadOnly property still breaks things.
Error is not in category a).
- if it is running in strict mode, then it must be new code that knows to catch the resulting exception.
Note that the Array, Function, Object, String and RegExp constructors would not be sealed, and the prototypes would still be mutable. So this proposal is not attempting to address the secure sublanguage issues (at least not on its own), only the issue with self-hosting.
Self-hosting is a specification and potentially an implementation detail,
My proposal addresses three issues:
- making it practical to use self-hosting in the specification;
- simplifying the expansions of syntactic sugar;
- allowing the run-times of secure sublanguages to refer to the unmodified constructors.
2 and 3 are just as important as 1.
not a reason for incompatible change that we could not ship in Firefox 3.
I think that your assertions of incompatibility are frankly overblown. Most of them do not even apply to what I proposed. Besides, we are talking about code that does not work in IE7 (user-provided constructors for Array, Function, Object and RegExp are not run [*]), and that in the case of Array, has behaviour that visibly changed between FF2 and FF3.
[*] a user-provided constructor for String is run, for some reason. See how inconsistent this is?
Good luck getting any other browser, including IE9 (in two years?) to do likewise.
With this design, it's no longer necessary to worry about rebinding or shadowing in the self-hosting specifications, or in the expansions of new syntactic sugar, provided that they only use the static methods and do not use Date.
Sorry, you're mistaken. The self-hosted Date needs to refer to its own original binding and properties.
That's trivial -- see the code below.
Etc.
What "etc."? If you want to imply that there are other problems, please state what they are.
I do not believe that anything important on the web
I don't know what to make of this. You're wrong, and I cited evidence to the contrary.
You cited no evidence that what I actually proposed would break. All of your examples in which concrete evidence of real code was provided were for names other than Array, Function, Object, String and RegExp.
But feel free to start a new browser project, break compatibility, grow your market share, and prove me wrong.
will break as a result of these changes -- and if it does, then I think it desperately needed to be broken as soon as possible.
This is wrong in at least six dimensions. You are not god-emperor of the web. The web is more like the boss of you. You must woo it, win it over with better ways to do things currently done via bad old APIs today, before you can remove those APIs and force your will on it.
This is why we have an insecure web. This attitude does a disservice to users. We are language designers; we should design, not roll over to accomodate the frightful code of every web developer who exploited obscure corner cases in the spec. Unless and until security is given a greater priority relative to compatibility, the web will stay insecure.
[...]
The self-hosting specification of Function.prototype.bind would be:
Object.defineProperty(Function.prototype, 'bind', { writable: false, enumerable: false, configurable: false, value: function(self, var_args) { const thisFunc = this; const leftArgs = Array.slice(arguments, 1); return function(var_args) { const args = Array.concat(leftArgs, Array.slice(arguments, 0)); return Function.staticApply(thisFunc, self, args); }; } });
(no 'use lexical scope' needed).
Let's see a Date method or three.
(function() { Object.defineProperty(global, 'NaN', { writable: false, configurable: false, enumerable: true, value: 0/0 }); Object.defineProperty(global, 'undefined', { writable: false, configurable: false, enumerable: true, value: void 0 }); Object.defineProperty(global, 'Infinity', { writable: false, configurable: false, enumerable: true, value: 1/0 });
// These are all straightforward arithmetic, no binding issues. function LocalTime(t) { ... } function UTC(t) { ... } function HourFromTime(t) { ... } function MinFromTime(t) { ... } function SecFromTime(t) { ... } function msFromTime(t) { ... } function MakeTime(hour, min, sec, ms) { ... } function MakeDay(year, month, date) { ... } function MakeDate(day, time) { ... } function TimeClip(time) { ... }
function CurrentTime() { ... } function TimeToString(time) { ... }
function Date_parse(string) { ... };
function Date_UTC(year, month, date, hours, minutes, seconds, ms) { return TimeClip(ComputeDate( year, month, date, hours, minutes, seconds, ms)); } };
const ToNumber = Object.Number;
// Used by Date constructor and Date.UTC. function ComputeDate(year, month, date, hours, minutes, seconds, ms) { year = ToNumber(year); month = ToNumber(month); date = date !== undefined ? ToNumber(date) : 1; hours = hours !== undefined ? ToNumber(hours) : 0; minutes = minutes !== undefined ? ToNumber(minutes) : 0; seconds = seconds !== undefined ? ToNumber(seconds) : 0; ms = ms !== undefined ? ToNumber(ms) : 0; if (year === year) { const iYear = Object.toInteger(year); if (0 <= iYear && iYear <= 99) year = 1900 + iYear; } const day = MakeDay(year, month, date); const time = MakeTime(hours, minutes, seconds, ms); return MakeDate(day, time); }
function Date(yearOrValue, month, date, hours, minutes, seconds, ms) { // assume proposed ES3.1 semantics for 'this' binding if (this === undefined) { return TimeToString(CurrentTime()); }
this.__Class__ = 'Date';
if (month !== undefined) {
this.__Value__ = TimeClip(UTC(ComputeDate(
yearOrValue, month, date, hours, minutes, seconds, ms)));
} else if (yearOrValue !== undefined) {
const value = Object.toPrimitive(yearOrValue);
const V = typeof value === 'string' ? Date_parse(value)
: ToNumber(value);
this.__Value__ = TimeClip(V);
} else {
this.__Value__ = CurrentTime();
}
}
Date.parse = Date_parse; Date.UTC = Date_UTC;
Object.defineProperty(Date, 'prototype', { writable: false, configurable: false, enumerable: true, value: Date.prototype });
Object.defineProperty(Date.prototype, 'constructor', { writable: false, configurable: false, enumerable: false, value: Date });
Object.defineProperty(Date.prototype, 'toString', { writable: true, configurable: true, enumerable: false, value: function() { if (this.Class !== 'Date') throw new TypeError(); return TimeToString(this.Value); } });
Object.defineProperty(Date.prototype, 'Class', { writable: false, configurable: false, enumerable: false, value: 'Date' });
Object.defineProperty(Date.prototype, 'Value', { writable: false, configurable: false, enumerable: false, value: NaN });
// We don't actually need Object.Date directly, but it might be useful // for secure subset run-times. Object.defineProperty(Object, 'Date', { writable: false, configurable: false, enumerable: true, value: Date });
global.Date = Date; })();
As you can see, it's trivial for the implementation of each "class" to use a local binding for the class name within a module, so that there is no issue with rebinding of the corresponding global. Some care is needed to only use local bindings of Writable properties -- for example, since Date.parse is defined by ES3 to be Writable (not ReadOnly), we need to use the local Date_parse instead in the Date constructor.
Note that this only works because Array, Function, Object, and String cannot be rebound, because we are using literals of those classes in the implementation. RegExp happened not to be used in the code above, but a more complete implementation might use it.
On Sep 11, 2008, at 12:39 AM, David-Sarah Hopwood wrote:
while IE7 mostly ignored changes to these bindings). So if there is any code relying on doing this,
at best it worked by accident.But it does work, and that's why we couldn't change it for Firefox 3.
No, it does not work cross-browser. The above code doesn't alert on
IE7, for instance.
Two points:
-
Web JS is often user-agent forked into the IE path and the
everything-else/Mozilla path. -
Pronoun trouble: the "it" in "it does work" refers to the code you
yourself said "works" ("by accident" does not alter "works"). You're
not free to break working code.
Also,
Array = function() { alert('hi'); }; [];
alerts on FF2 but not IE7.
Yes, I know all about FF2 and 3 :-/. This is beside the point.
Namespace is from ECMA-357, E4X. It's relevant because we do not
want to special-case the constructor binding rules per standard object.Well, that is exactly what my proposal does (and I explain why below). I'd appreciate it if you would address your criticism to what I
actually proposed, rather than something I didn't.
I do address what you propose later, and I criticize it right here
for making two (or five, or six+ for Error and friends) special cases
where there should be one rule. So who is not addressing a criticism?
There are exactly two categories of name in my proposal:
a) Array, Function, Object, String and RegExp b) Everything else.
Now that's rude -- you seemingly just ignored my pointing out Error,
SyntaxError, etc. Or are you rehashing your proposal in advance of
adapting it to the facts I pointed out about other standard classes
being memoized?
I'll try not to get in a huff back at you, but come on! Your message
is way too long and preciously argued.
There are several strong arguments for treating a) differently from
b). To expand on them:
- If you allow rebinding the names in a), you must specify precisely what it means to do so (otherwise allowing it is useless).
There's a very significant specification complexity cost in doing that.
I'm proposing that the original values be memoized and used, and that
the bindings be read/write -- as they've been since 1995. You are
proposing something (a) more complicated because variegated; (b) web-
incompatible. Why?
Note that if creating an object or a function runs the user- specified Object or Function constructor, then that constructor will not be able to do anything useful, because creating any object or function in it will cause an infinite regress.
This is a straw man. No uprev browser implements it, no one is
proposing it.
If creating an object or function does not run the user-specified constructor, OTOH, then what was the point of rebinding it?
Are you begging the question? My objection was that web content does
rebind, and expects its bindings to "stick" -- not to be used for
constructing objects or functions, but simply to read back as
written. Doing otherwise is web-incompatible.
Did you read the bugs I listed, especially the last one? It looks
like I'll have to wade through hundreds of words to find out.
- Existing JS implementations do not handle rebinding of the names in a) consistently (as demonstrated by the examples above).
Sorry, Firefox 2 is downrev and not influencing web content authors.
It does not count as "examples", plural.
You use selective and marginal arguments to make a sweeping proposal
for complicated incompatibility. That is not how web compatibility
works.
There is inconsistency between browsers, between browser versions, and between particular names in each version. Any code that rebinds these names works only by coincidence,
But it works. Let's get that straight.
and is liable to break at any time,
No, only if we adopt your proposal.
regardless of whether it is nominally allowed by the spec.
The spec is an ass.
- Allowing these names to be rebound inhibits useful optimizations.
Only if you ignore the proposal at the bottom of the "which
prototype" wiki page I pointed you at, which browsers mostly or
completely implement in their latest versions: memoize but allow
rebinding.
For example, it inhibits the optimization that is assumed to be possible in the rationale document for the Object static methods. (Maybe this optimization can be recovered if it is done only within an 'if' block that tests 'Object === original_Object', and does not itself rebind 'Object'. But I think it is an unreasonable amount of implementation complexity to detect this case.)
Both "use strict" and "use lexical scope" have variously proposed to
fix this in a backward compatible way.
- The names in a) (and static methods on them) are those that we need to use to write self-hosting specifications, and that might be used in the expansions of syntactic sugar added in ES-Harmony. It is, I believe, completely infeasible to write any ECMAScript code that has predictable behaviour if these names cannot be relied on. (It's difficult enough if these are the only names that can be relied on, but it is possible, with care.)
Hence the "use lexical scope" proposal, and "use strict" proposals
that bind constructors immutably.
- Existing web content relies on rebinding the names in b), but not (or to a much lesser extent) the names in a).
Where is your evidence for "to a much lesser extent"?
We want one rule to bind them all,
That would be nice, but I don't believe that this consideration should override the arguments given above.
Your arguments are bogus, in short. You've bootstrapped a complicated
and incompatible proposal from the thin premise that the spec and
implementations are inconsistent. I've rebutted your specific
arguments, where they don't assume their conclusions. But onward,
many hundreds to thousands more words to go, apparently:
ES3 is already rather haphazard in its assignment of attributes, particularly ReadOnly. (I'm not defending that, just pointing out that what we are starting from in how ES3 specifies attributes with is very far from "one rule to bind them all".)
What does ReadOnly have to do with anything? The problem is that
all standard constructor bindings are read/write, and I'm not
proposing to change that.
Again you seem to be saying "messy" implies "can change freely as I
see fit". No web browser vendor would be in business for long if they
acted on such a belief.
partly for simplicity of implementation,
The implementation is trivial, using Object.defineProperty. So is the specification. We can add a footnote pointing out the irregularity, so that implementors can't miss it.
There's nothing simple about Object.defineProperty. I invite
onlookers to read the latest ES3.1 spec, if they have time:
doku.php? id=es3.1:es3.1_proposal_working_draft (top-most pdf link)
No offense to the authors, it is what it is. Simple, it ain't!
Anyway, using it as a black box in an irregular, arbitrary, and web- incompatbile way is non-trivial. It's also not going to fly.
mostly for cognitive simplicity for programmers using JS,
Cognitive simplicity is thrown out of a 100th-storey window for any code that rebinds the names in a).
No, not if the single rule is memoization.
Even if it were allowed, it would be necessary to avoid doing this if cognitive simplicity for programmers is a goal.
You are now conflating spec and implementation complexity with the
completely separate issue of the complexity of real-world code on the
web that rebinds Error, etc.
Please keep these separate.
I agree that allowing names of built-in global constructors other than those in a) to be rebound, is slightly irregular. There's a simple solution to that: don't write code that rebinds other built-in global constructors either.
Again you are conflating. Your admonitions are meaningless to web
content authors who may be long gone, and who are likely not
listening in any event. As for spec authors and TC39 members
including browser vendors, you are preaching to the choir. It is not
we who wrote the code that depends on the read/write bindings for
Object, etc.
Just because it is allowed (for compatibility), doesn't mean that it should be done. In fact we should probably explicitly deprecate it (which would potentially allow making all built-in global constructor names non-Writable and non-Configurable in some future version).
Deprecation is almost meaningless without a carrot to induce content
rewriting. Only retrospectively can you hope to obsolete anything.
We've done this over the years in Mozilla (originally Netscape) code;
so has IE. It takes a decade sometimes; it can't be prescribed or
predicted.
So most of the compatibility issues described in these regressions are simply not relevant to what I proposed.
See above. Do you think our beta was some kind of exhaustive test?
I am perfectly happy to let this code break.
Good luck with your browser launch :-P.
You ignored this one. Why?
Error is not in category a).
(But suppose for the sake of argument that it were. This code says "root.Error=function(){};", where 'root' refers to the global object. Since it is run in non-strict mode, the assignment to a non-Writable property will be silently ignored -- which is completely harmless in this case. In fact it is desirable, since the actual constructor for Error might do something important in a given implementation.
Except that ES3 does not specify reflecting on the current binding of
Error, SyntaxError, etc. when throwing a specified exception. So the
remaining risk is that the MSN code would want to read back what it
wrote, or expect explicit use of its rebound Error to work as
implemented by that interpreted function.
It looks like neither of these is required, but it also looks like
this file is a dead limb. But I cited it to point out the likelihood
of overrides for constructors that are memoized (including
prototypes). Which puts Error, etc., in your first category.
You can't have it both ways. Either the rebinding is not allowed
because literal syntax or internal construction for throw reflects on
the binding, or rebinding is allowed. That's your over-complicated
proposal in a nutshell. So why wouldn't Error, etc. be in the first
category?
Assuming Error etc. are ReadOnly, then the problem remains: is this
too incompatible a change to get away with? The only way to find out
is to ship and see what bugs get filed. Alternately hand-waving away
real evidence to the contrary, or moralizing about bad code deserving
to be broken, simply won't cut it.
The same argument, of course, applies to assignments to Array,
Function, Object, String and RegExp, where the code either happens to work,
or was deliberately written to work if the assignment is silently ignored.)
You're assuming your own conclusion again. The bug you ignored:
bugzilla.mozilla.org/show_bug.cgi?id=412324
"Main content panel is not rendered for all WebCT/Blackboard
installations"
contains links to the code we broke, which includes
function Error(title, message) { this.title = title; this.message = message; }
function addError(title, message) { errors.push(new Error(title, message)); }
This is exactly a case where was was written to Error must read back
and behave as implemented. Please stop ignoring this evidence.
Now it is possible for any code that needs to refer to the original constructors, including the specification itself, to do so easily. For example 15.1.2.1 step 2 would just say "throw an Object.SyntaxError exception".
This may or may not be a good idea (I will object to any such bloat
in Object if we can keep spec simpler and give people "use lexical
scope"). It has nothing to do with the bone of contention.
This is reasonable because there are only a relatively small number of places in the spec where Error objects are created (78, by my
count). If we were to do the same thing for Array, Function, String and
RegExp, then we'd have to make huge changes to the specification; it would be completely impractical.
Then don't do that. Why did you even entertain the idea?
I'm not in favor of Object.Array or any such nonsense. I'm very much
opposed to incompatible changes without opt-in. And I'd rather we
have fewer than more rules for what changes when you opt-in, so "use
lexical scope" or even a "use strict" change to make all constructor
bindings immutable would be fine.
But not Object.RegExp!
Besides, making all of those changes would imply that it would not be useful to rebind Array, Function, String and RegExp, even if it were possible, because the rebinding would effectively be ignored (or
worse, it would be used inconsistently, with some objects using the rebound prototypes and some not).
No one is proposing that straw man. Please stop setting it up. Modern
browsers don't even disagree much: they memoize.
No; bugzilla.mozilla.org/show_bug.cgi?id=412324 is just a
taste of what you'll get in the way of bug reports, I predict.That bug involves code that rebinds Error, which is not in category
a).
Why shouldn't it be in category (a)? You haven't said, you've only
proposed a silly Object.Error way to keep an immutable binding set
aside for reflection.
I think you are missing a few classes (e.g. Error).
The omission of the Error constructors was quite deliberate. They're not created by literals, they're not important to optimize, and it is easy to work around their rebinding, as described above.
This is absurd. You can't invent an Object.Error immutable binding to
keep Error out of your own category (a), yet not do the same for
Array, say, or RegExp.
Having such complicated rules also seems bad. There will be more built-in classes. And what about "host constructors" like Image,
Option, HTMLDivElement, etc.? Why should they have different binding rules?They don't; they're like everything else in category b).
Why? You assert your conclusion instead of justifying it with
arguments from shared premises.
Lack of literal syntax might be one argument, but <div> does create
an HTMLDivElement in the DOM.
But I'm not going to guess. You'll have to say exactly why only five
standard classes need immutable bindings (I'll ignore the
incompatibility for the moment).
Having two categories of global name is not especially complicated.
It's worse than one, which is what browsers have done since 1995, and
what I'm proposing without counter-argument from you.
I also think you're underestimating the extent to which this approach can be used to simplify the specification.
I'm sure you are underestimating complexity.
My proposal addresses three issues:
- making it practical to use self-hosting in the specification;
Self-hosting is only one goal, and it can be better met with "use
lexical scope" and Object.defineProperty.
- simplifying the expansions of syntactic sugar;
Making two categories of constructor bindings complexifies.
- allowing the run-times of secure sublanguages to refer to the unmodified constructors.
Or not, unless the Object.Error bloat is added.
Sorry, "use lexical scope" wins here too, and has added benefits beyond.
2 and 3 are just as important as 1.
3 is an experiment, or a bet, or possibly unfalsifiable -- a
religious belief.
1 and 2 are important enough, but your proposal is over-complicated
because irregular and arbitrary.
I think that your assertions of incompatibility are frankly overblown.
Prove it.
Most of them do not even apply to what I proposed.
But the ones that do, like Error, you ignore. That's rude.
Besides, we are talking about code that does not work in IE7 (user-provided
constructors for Array, Function, Object and RegExp are not run [*]),
You're confused. No web developer expects user-defined constructors
to be run for literals in uprev browsers.
[*] a user-provided constructor for String is run, for some reason. See how inconsistent this is?
IE JScript has many bugs, more than other implementations. So what?
The point is that modern browsers do not follow ES3's object and
array initialiser lunacy of evaluating "new Object" or "new Array".
Good. No one wants this. So stop using its lack as a reason for
making incompatbile change to the binding of Object and Array.
The read/write bindings and the memoized internal constructors/
prototypes are separate concerns, because compatibility is king. Deal
with this point directly.
Etc.
What "etc."? If you want to imply that there are other problems,
please state what they are.
The rest of the self-hosting.
I do not believe that anything important on the web
I don't know what to make of this. You're wrong, and I cited
evidence to the contrary.You cited no evidence that what I actually proposed would break. All of your examples in which concrete evidence of real code was provided were for names other than Array, Function, Object, String and RegExp.
You're right, except for Error being excluded from this bogus
category, and for the objection to having two categories. But you are
right: we didn't implement your over-complicated proposal and try
shipping it in Firefox 3 beta.
Sorry, it didn't occur to us to try anything so complicated and
arbitrary -- why wouldn't we do the same for Error, in particular?
You still have not given any definition to distinguish that case.
Arguing for putting a permanent Object.Error binding in a future
edition does not say why this is necessary, or why such a strange
solution would not apply to Array too.
This is wrong in at least six dimensions. You are not god-emperor
of the web. The web is more like the boss of you. You must woo it, win it
over with better ways to do things currently done via bad old APIs today, before you can remove those APIs and force your will on it.This is why we have an insecure web.
No, this is why we have a web at all.
This attitude does a disservice to users. We are language designers; we should design, not roll over to accomodate the frightful code of every web developer who exploited obscure corner cases in the spec. Unless and until security is given a greater priority relative to compatibility, the web will stay
insecure.
You've lost me. How does security, a set of properties of whole
systems, arise from your making the bindings of five constructors
(out of many) immutable?
You can give the self-congratulation a rest.
As you can see, it's trivial for the implementation of each "class"
to use a local binding for the class name within a module, so that there
is no issue with rebinding of the corresponding global. Some care is needed to only use local bindings of Writable properties -- for example,
since Date.parse is defined by ES3 to be Writable (not ReadOnly), we need to use the local Date_parse instead in the Date constructor.
Yes, that's part of the challenge. But you missed this:
Object.defineProperty(Date.prototype, 'toString', { writable: true, configurable: true, enumerable: false, value: function() { if (this.Class !== 'Date') throw new TypeError(); return TimeToString(this.Value); } });
What binding of TypeError is used there?
Note that this only works because Array, Function, Object, and
String cannot be rebound, because we are using literals of those classes
in the implementation.
Yeah, if you assume your conclusion then it's true.
RegExp happened not to be used in the code above, but a more complete implementation might use it.
I am glad you ended with code. We can reason about it together.
Please use fewer words above the code next time, and respond to the
specific arguments with specific counter-arguments. "Messy" is not
the root password to the incompatible change super-user account.
On Sep 11, 2008, at 2:39 AM, David-Sarah Hopwood wrote:
This is why we have an insecure web. This attitude does a disservice
to users. We are language designers; we should design, not roll over to accomodate the frightful code of every web developer who exploited obscure corner cases in the spec.
Sorry David, but this is exactly backwards. If user Jane, mother of
3, who doesn't know the first thing about "JavaScript" or how the web
works, visits a web page that works in browser A but breaks in browser
B, then she'll think browser B is broken, thus she'll see browser B as
an inferior product. This is the competitive force that drives
backward compatibility requirements in the web browser marketplace. I
wouldn't call it "rolling over to accommodate...every web developer,"
it's more of a kind and gracious accommodation of the real customer,
the browser user.
On 2008-09-10, at 03:49EDT, Brendan Eich wrote:
Constructors can't be .apply'ed when invoked via operator new -- a bug that the proposed and so-far warmly- received spread operator solves nicely:
new callable(...argsarray)
Note no explicit |this| parameter for constructors, so spread does not solve the Function.prototype.apply problem for plain calls:
Safe.universalApply(callable, thisobj, argsarray)
can't be rewritten as:
callable(this=thisobj, ...argsarray)
as Tucker suggested recently (he used receiver=, but I still think this assignment is better, since it's illegal currently and it avoids adding a new contexually reserved word -- detail!).
It's great when you read more into my comments than I intended :)
I was proposing being able to rename this
in the body of a method by
naming it in the formals (parameter) list. My purpose was to make it
easier to understand when this
is being closed over in an internal
function. You are proposing being able to assign to it in the actuals
(arguments) list. A nice parallelism.
Still, if we had spread, would we be bothering with Function.apply, or with those pesky strict mode arguments tweaks? Food for thought. Sometimes adding new syntax avoids silly name mangling and unnecessary modalities.
Perhaps?
Function.prototype.bind = function(this=thisFunc,
context, ...partial) {
return function(...rest) {
return thisFunc(this=context, ...partial, ...rest);
}
}
On Wed, Sep 10, 2008 at 6:33 PM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:
Garrett Smith wrote:
On Wed, Sep 10, 2008 at 3:41 PM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:
Mark S. Miller wrote:
On Tue, Sep 9, 2008 at 9:51 PM, Brendan Eich <brendan at mozilla.org> wrote:
Why would "Function.apply3" need three arguments?
Because Function.prototype.apply takes two arguments, and apply3 (or staticApply, or whatever it is called) makes the 'this' argument explicit.
Function.prototype.apply doesn't actually need any arguments.
No, it always takes two arguments, which may be undefined. But since an undefined argument can't be distinguished from a missing one, that does mean it's not possible to condition on whether there are two or three arguments. (Besides, I withdrew the suggested name 'apply3'.)
No, it doesn't always take two arguments. It takes whatever arguments I pass it. I just called it with 0 arguments and it took that.
A missing argument could absolutely be distinguished from an argument.
function countArgs(a, b) { if(a === undefined && b === undefined) { alert(arguments.length); } }
When a countArgs is enterered, the Variable object gets the properties |a| and |b|. If the caller supplied fewer arguments than the number or formal parameters, the extra formal parameters have value |undefined|
The |arguments| object is initialized with a |length| property that holds the number of actual parameter values supplied by the caller.
Thus:
countArgs(undefined, undefined);
elerts "2"
See for yourself:-
Garrett
Garrett Smith wrote:
On Wed, Sep 10, 2008 at 6:33 PM, David-Sarah Hopwood <david.hopwood at industrial-designers.co.uk> wrote:
Garrett Smith wrote:
Function.prototype.apply doesn't actually need any arguments.
No, it always takes two arguments, which may be undefined. But since an undefined argument can't be distinguished from a missing one,
I had forgotten arguments.length, but it doesn't matter.
that does mean it's not possible to condition on whether there are two or three arguments. (Besides, I withdrew the suggested name 'apply3'.)
No, it doesn't always take two arguments. It takes whatever arguments I pass it. I just called it with 0 arguments and it took that.
Since I withdrew the suggested name 'apply3', how many arguments it (or Function.prototype.apply) is considered to take is no longer interesting or important. Presumably there is no dispute that the static version's first argument is required, and that its second and third arguments are optional.
After having used my own version of an implementation of Function.prototype.bind for about 4 years now I recently became aware of the one that appears to now be standardised in ES5.
The following page suggests its definition stems from the Prototype.js library: proposals:static_generics
The page argues that "if we do this, we should emulate Prototype, including its 'pre-args' feature, since it actually hacks on Function.prototype and it's a de-facto standard".
I have to disagree, especially with this so-called 'pre-args' feature, which prepends arguments to the final argument list. There are quite a few libraries out there, Prototype.js is only one of many, and seems to have lost significance over the past years. Calling it a de-facto standard and shaping parts of the future ECMAScript standard after it in order to remain compatible with it seems questionable. There are other areas were similar considerations seem to have played a role, the oddly named Object.keys() comes to mind.
The current implementation of bind() uses the first argument as the bind object, and the ones after as the arguments passed to the bound function. If the bound function then is called with passed arguments itself, these are appended to the argument list that was defined in the call of bind(). I find this confusing and wonder if there is any real world use for this? I have no experience with Prototype.js but never saw the need for such a feature. Here an example to illustrate the functionality of bind():
function test() { alert(Array.prototype.slice(arguments).join(', ')); }
test(1, 2, 3); // "1, 2, 3"
var test2 = test.bind(this, 1, 2, 3);
test2(); // "1, 2, 3"
test2(4, 5); // "1, 2, 3, 4, 5"
Am I the only one thinking it is wrong that the first and second argument to a function then actually end up being the 4th and the 5th when the originally defined function is called? If test() was defined differently and would do more than simply join the arguments to one string but actually used parameter names to perform something with them, I would find the resulting code to be pretty hard to read and understand.
An alternative definition could have been that the bind() call defines the default arguments that are passed to the bound function, which can be overridden when the bound function is called. The above example would end up as:
test2(4, 5); // "4, 5, 3"
This seems more logical to me, and less prone to errors.
Another issue I have with bind() is that there is no alternative version of it that receives an array for the arguments passed to the function, in analogy to the differences between apply() and call(). If I had to choose only one of these two, I would opt for apply(), as it offers far more flexibility. A bind() function defined to receive the arguments as an array would still allow things like the above example with very little more syntax, while offering many other advantages at the same time:
var test2 = test.bind(this, [ 1, 2, 3 ]);
or:
var args = [ 1, 2, 3 ];
if (passMoreArgs) args.push(4, 5, 6);
var test2 = test.bind(this, args);
I hope this serves as an understandable illustration.
With many of the recent additions to the language I continue to be a bit puzzled by the decision making process. I think things should not just be taken over from a library that is at one point in fashion. Functionality should be discussed and formulated in a way that feels true to the philosophies in the ECMAScript language. Such libraries are good because they point out things currently missing in the language. But they should be seen as starting points for a dialogue, not as the final word.
Jürg
On Apr 30, 2010, at 6:32 AM, Jürg Lehni wrote:
After having used my own version of an implementation of
Function.prototype.bind for about 4 years now I recently became
aware of the one that appears to now be standardised in ES5.The following page suggests its definition stems from the
Prototype.js library: <proposals:static_genericsThe page argues that "if we do this, we should emulate Prototype,
including its 'pre-args' feature, since it actually hacks on
Function.prototype and it's a de-facto standard".I have to disagree, especially with this so-called 'pre-args'
feature, which prepends arguments to the final argument list. There
are quite a few libraries out there, Prototype.js is only one of
many, and seems to have lost significance over the past years.
Calling it a de-facto standard and shaping parts of the future
ECMAScript standard after it in order to remain compatible with it
seems questionable.
It's a fair point. Perhaps we prematurely standardized
Function.prototype.bind.
But did you know that Dojo's hitch also allows leading arguments to be
bound too? Likewise MooTools's bind. Only jQuery's proxy does not.
I always say that it is early days in the JS library standardization
process, and we should not prematurely standardize. Nevertheless I
think ES5 is better off with Function.prototype.bind than without. We
could have done nothing to serve this use-case, but that seems
strictly worse. We could have done something else but it would have
been under a different name.
Instead we went with what seemed the de-facto standard. It's still a
point in common among several top libraries.
There are other areas were similar considerations seem to have
played a role, the oddly named Object.keys() comes to mind.
We've covered this recently. It was one of Doug Crockford's
suggestions, IIRC. I like Doug's generally shorter names, but it
indeed does not fit with Object.getOwnPropertyNames. This is a design-
by-committee artifact, IMHO. We'll try to do better in the future in
unifying name styles.
Brendan,
Thank you for clarifying.
But did you know that Dojo's hitch also allows leading arguments to be bound too? Likewise MooTools's bind. Only jQuery's proxy does not.
I did not know about Dojo's hitch, but I believe Mootools' bind is not behaving in the same way. It receives an array as a second argument, defining the arguments to be passed to the bound functions, and appears to ignore any other parameters passed to it when calling. I actually prefer this passing of arguments in an array over the standardised bind, for reasons explained in my previous email.
But this means libraries like MooTools automatically break the JavaScript standard, as now Function.prototype.bind gets overridden with an incompatible version. And it also is too late for MooTools to change that now, as such a change would break all depending code at once.
This then raises other interesting questions: Should libraries refrain completely from polluting any of the default prototypes? How else can one be sure there won't be a standardised property in the future that might overlap with functionality of a library in such a way?
I always say that it is early days in the JS library standardization process, and we should not prematurely standardize. Nevertheless I think ES5 is better off with Function.prototype.bind than without. We could have done nothing to serve this use-case, but that seems strictly worse. We could have done something else but it would have been under a different name.
Instead we went with what seemed the de-facto standard. It's still a point in common among several top libraries.
I can only see one library that exactly follows the standard, or the other way round; Prototype.js. Dojo's hitch does many other things as well, but it might be able to use the native bind now to optimise that particular case. MooTools' bind shares the name but behaves differently. And jQuery is as always fine as it does not mess with the prototypes.
In this case I get the feeling everybody could have benefitted from a Function.bind generic that all the different implementations in the prototype could refer to, but nothing standard in the Function.prototype object.
Such a Function.bind could then have been defined to receive the arguments in one array object, and to concatenate them with the ones passed to the returned function when calling. This would have allowed all the different implementations mentioned above to use it in an efficient way.
But I guess there is no point crying over spilled milk, and I agree, it is better to have the current Function.prototype.bind than nothing at all.
At the same time I believe it is also important to understand that libraries are good sources of hints for what needs to be improved in the language, but not necessarily for how, as such libraries do not have the same amount of freedom in the formulation of functionality, and often aim for solutions that produce little source code or can reuse functionality defined elsewhere in the library. This is then not necessarily the right way to implement it natively.
Jürg
On Apr 30, 2010, at 5:33 PM, Jürg Lehni wrote:
But did you know that Dojo's hitch also allows leading arguments to
be bound too? Likewise MooTools's bind. Only jQuery's proxy does not.I did not know about Dojo's hitch, but I believe Mootools' bind is
not behaving in the same way. It receives an array as a second
argument, defining the arguments to be passed to the bound
functions, and appears to ignore any other parameters passed to it
when calling. I actually prefer this passing of arguments in an
array over the standardised bind, for reasons explained in my
previous email.
MooTools takes an array of actual arguments to bind, while the others
take trailing positional actual params, I see. My point was that
however they are passed, these values are bound to the leading
arguments supplied when the bound method is invoked.
But this means libraries like MooTools automatically break the
JavaScript standard, as now Function.prototype.bind gets overridden
with an incompatible version. And it also is too late for MooTools
to change that now, as such a change would break all depending code
at once.
It's an either-or situation. Users of MooTools would have to save
Function.prototype.bind under a different name if they want to use the
original (or prior) value of this (possibly ES5, but perhaps
Prototype) method.
It's the same drill when mixing libraries. Some cooperate better than
others do, or did -- I believe many can be mixed now without trouble.
In this light, ES5's standard object additions are just another
library that happens to be loaded before any others. This can affect
object-detecting code, certainly. It's not 100% compatible with ES3 on
this basis alone.
But, as you noted about spilled milk, ES5 is a done deal as a
standard, and it is getting implemented. We'll just have to learn from
the experience and keep working to do better.
This then raises other interesting questions: Should libraries
refrain completely from polluting any of the default prototypes? How
else can one be sure there won't be a standardised property in the
future that might overlap with functionality of a library in such a
way?
Probably. Prototype, I'm told, is not going to change to avoid
extending some built-in core-language prototypes, e.g.
Array.prototype, but it will stop trying to extend DOM prototypes.
Clarification from library authors is welcome here.
I always say that it is early days in the JS library
standardization process, and we should not prematurely standardize.
Nevertheless I think ES5 is better off with Function.prototype.bind
than without. We could have done nothing to serve this use-case,
but that seems strictly worse. We could have done something else
but it would have been under a different name.Instead we went with what seemed the de-facto standard. It's still
a point in common among several top libraries.I can only see one library that exactly follows the standard, or the
other way round; Prototype.js. Dojo's hitch does many other things
as well, but it might be able to use the native bind now to optimise
that particular case. MooTools' bind shares the name but behaves
differently. And jQuery is as always fine as it does not mess with
the prototypes.
Yes, jQuery nicely avoids the whole prototype issue.
In this case I get the feeling everybody could have benefitted from
a Function.bind generic that all the different implementations in
the prototype could refer to, but nothing standard in the
Function.prototype object.
It could be that we missed a chance to add Function.bind(callable,
thisObj, argsArray). Adding such a "static method" might have provided
the desired common utility usable without conflicts by libraries. We
may yet add such a Function.bind.
I should mention the spread operator as a way to solve the positional
vs. arrayed arguments issue: harmony:spread
-- no more .apply and .call twinning, no conundrums of the "how do I
apply the new operator" kind.
At the same time I believe it is also important to understand that
libraries are good sources of hints for what needs to be improved in
the language, but not necessarily for how, as such libraries do not
have the same amount of freedom in the formulation of functionality,
and often aim for solutions that produce little source code or can
reuse functionality defined elsewhere in the library. This is then
not necessarily the right way to implement it natively.
Agreed, and I think TC39 takes this to heart (perhaps more now than
before when the draft standard was being put together ;-).
On Apr 30, 2010, at 5:33 PM, Jürg Lehni wrote:
Brendan,
Thank you for clarifying.
But did you know that Dojo's hitch also allows leading arguments to be bound too? Likewise MooTools's bind. Only jQuery's proxy does not.
I did not know about Dojo's hitch, but I believe Mootools' bind is not behaving in the same way. It receives an array as a second argument, defining the arguments to be passed to the bound functions, and appears to ignore any other parameters passed to it when calling. I actually prefer this passing of arguments in an array over the standardised bind, for reasons explained in my previous email.
But this means libraries like MooTools automatically break the JavaScript standard, as now Function.prototype.bind gets overridden with an incompatible version. And it also is too late for MooTools to change that now, as such a change would break all depending code at once.
Libraries that extend intrinsic prototypes deserve any and all pain that comes to them. Their users should likewise be warned of the impending pain; but no affordance should be made for their poor decision making when it comes to designing a better API for users of the language.
This then raises other interesting questions: Should libraries refrain completely from polluting any of the default prototypes?
Yes, absolutely, 100%.
How else can one be sure there won't be a standardised property in the future that might overlap with functionality of a library in such a way?
And why is that the language's job? As a library author, the goal for me of a library was always as a stop-gap until the cavalry arrived. Now that they're showing up, I'm glad to un-plant my flag and cede the high ground to the committee.
I always say that it is early days in the JS library standardization process, and we should not prematurely standardize. Nevertheless I think ES5 is better off with Function.prototype.bind than without. We could have done nothing to serve this use-case, but that seems strictly worse. We could have done something else but it would have been under a different name.
Instead we went with what seemed the de-facto standard. It's still a point in common among several top libraries.
I can only see one library that exactly follows the standard, or the other way round; Prototype.js. Dojo's hitch does many other things as well, but it might be able to use the native bind now to optimise that particular case. MooTools' bind shares the name but behaves differently. And jQuery is as always fine as it does not mess with the prototypes.
Dojo's hitch doesn't touch prototypes. With non-intrinsic-munging libraries do is between them and consenting users. It's only libraries that are foolish enough to extend intrinsics that are at any risk here, and as I said, they deserve it.
In this case I get the feeling everybody could have benefitted from a Function.bind generic that all the different implementations in the prototype could refer to, but nothing standard in the Function.prototype object.
Such a Function.bind could then have been defined to receive the arguments in one array object, and to concatenate them with the ones passed to the returned function when calling. This would have allowed all the different implementations mentioned above to use it in an efficient way.
But I guess there is no point crying over spilled milk, and I agree, it is better to have the current Function.prototype.bind than nothing at all.
At the same time I believe it is also important to understand that libraries are good sources of hints for what needs to be improved in the language, but not necessarily for how, as such libraries do not have the same amount of freedom in the formulation of functionality, and often aim for solutions that produce little source code or can reuse functionality defined elsewhere in the library. This is then not necessarily the right way to implement it natively.
Jürg
es-discuss mailing list es-discuss at mozilla.org, mail.mozilla.org/listinfo/es-discuss
-- Alex Russell slightlyoff at google.com alex at dojotoolkit.org BE03 E88D EABB 2116 CC49 8259 CF78 E242 59C3 9723
On Fri, Apr 30, 2010 at 5:50 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Apr 30, 2010, at 5:33 PM, Jürg Lehni wrote:
[...]
Yes, jQuery nicely avoids the whole prototype issue.
What do the spec contributors have to say about scripts that modify built-ins?
The concept "don't modify objects you don't own" can be a hot topic for some. What does the committee have to say on that?
Mentioned in elsehwere: jibbering.com/faq/notes/code-guidelines, www.nczonline.net/blog/2010/03/02/maintainable-javascript-dont-modify-objects-you-down-own
[...]
It could be that we missed a chance to add Function.bind(callable, thisObj, argsArray). Adding such a "static method" might have provided the desired common utility usable without conflicts by libraries. We may yet add such a Function.bind.
Function.bind is already specified, isn't it? Function itself is a function.
[...]
Garrett
On Apr 30, 2010, at 10:28 PM, Garrett Smith wrote:
It could be that we missed a chance to add Function.bind(callable,
thisObj, argsArray). Adding such a "static method" might have provided the
desired common utility usable without conflicts by libraries. We may yet
add such a Function.bind.Function.bind is already specified, isn't it? Function itself is a
function.
Oops, of course -- it would have to be called something else, or
located somewhere else.
And why is that the language's job? As a library author, the goal for me of a library was always as a stop-gap until the cavalry arrived. Now that they're showing up, I'm glad to un-plant my flag and cede the high ground to the committee.
But this raises the question even more: Why does the cavalry once it arrives just take over one of the preliminary designs and not try to improve upon it, although it has so much more resources available?
Also, could you show me a real world use case of the argument concatenation feature found in hitch and bind? I am not working with Prototype nor Dojo, but so far I have not come to face a situation where this would have been useful. The resulting code seems rather confusing to me, very hard for someone else than the writer to figure out what is actually going on.
Dojo's hitch doesn't touch prototypes. With non-intrinsic-munging libraries do is between them and consenting users. It's only libraries that are foolish enough to extend intrinsics that are at any risk here, and as I said, they deserve it.
But it appears it was libraries that actually did touch intrinsics that in the end influenced the standardisation process, so they did have an impact. Maybe they are also important in the process of understanding how the language itself could be shaped and extended, without the use of generics everywhere that feel a bit like working through the holes of a glovebox?
While I generally agree with your points, it just seems a bit hard to then say they deserve it...
Jürg
But would that really be a problem?
Function as an instance could have a different definition of bind that overrides the one from prototype.
I can see how this is maybe not most elegant approach, but would you ever need to bind a constructor to another object?
Jürg
On 1 May 2010, at 01:50, Brendan Eich wrote:
MooTools takes an array of actual arguments to bind, while the others take trailing positional actual params, I see. My point was that however they are passed, these values are bound to the leading arguments supplied when the bound method is invoked.
I just performed the same test on Mootools, no argument concatenation seems to happen there:
function test() { alert($A(arguments)); }
test(1, 2, 3); // "1, 2, 3"
var test2 = test.bind(this, [1, 2, 3]);
test2(); // "1, 2, 3"
test2(4, 5); // "1, 2, 3
On May 1, 2010, at 3:28 AM, Jürg Lehni wrote:
On 1 May 2010, at 01:50, Brendan Eich wrote:
MooTools takes an array of actual arguments to bind, while the
others take trailing positional actual params, I see. My point was
that however they are passed, these values are bound to the leading
arguments supplied when the bound method is invoked.I just performed the same test on Mootools, no argument
concatenation seems to happen there:function test() { alert($A(arguments)); }
test(1, 2, 3); // "1, 2, 3"
var test2 = test.bind(this, [1, 2, 3]);
test2(); // "1, 2, 3"
test2(4, 5); // "1, 2, 3
Odd, that is neither fish nor fowl. Does any other library have such a
bind?
Anyway, Moo was not around when TC39 was looking at bind, or at least,
it was not on our radar.
On Sat, May 1, 2010 at 7:13 AM, Brendan Eich <brendan at mozilla.com> wrote:
On May 1, 2010, at 3:28 AM, Jürg Lehni wrote:
On 1 May 2010, at 01:50, Brendan Eich wrote:
[...]
Odd, that is neither fish nor fowl. Does any other library have such a bind? Anyway, Moo was not around when TC39 was looking at bind, or at least, it was not on our radar.
YUI had bind that did a right bind. From a glance it looks like that functionality is changed, and I see they now have a function called rbind.
Ext.js has a flag for their bind function to use either right bind or left bind.
On Fri, Apr 30, 2010 at 11:07 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Apr 30, 2010, at 10:28 PM, Garrett Smith wrote:
It could be that we missed a chance to add Function.bind(callable, thisObj, argsArray). Adding such a "static method" might have provided the desired common utility usable without conflicts by libraries. We may yet add such a Function.bind.
Function.bind is already specified, isn't it? Function itself is a function.
Oops, of course -- it would have to be called something else, or located somewhere else.
Yeah, a different name, if at all.
Static Array Generics don't have that problem, though, e.g. Array.slice(hostObj) ...
Garrett
On Sat, May 1, 2010 at 10:13 AM, Brendan Eich <brendan at mozilla.com> wrote:
On May 1, 2010, at 3:28 AM, Jürg Lehni wrote:
On 1 May 2010, at 01:50, Brendan Eich wrote:
MooTools takes an array of actual arguments to bind, while the others take trailing positional actual params, I see. My point was that however they are passed, these values are bound to the leading arguments supplied when the bound method is invoked.
I just performed the same test on Mootools, no argument concatenation seems to happen there:
function test() { alert($A(arguments)); }
test(1, 2, 3); // "1, 2, 3"
var test2 = test.bind(this, [1, 2, 3]);
test2(); // "1, 2, 3"
test2(4, 5); // "1, 2, 3
Odd, that is neither fish nor fowl. Does any other library have such a bind?
Anyway, Moo was not around when TC39 was looking at bind, or at least, it was not on our radar.
I see that Dojo does same thing as Prototype — concats bound and "runtime" arguments together (api.dojotoolkit.org/jsdoc/HEAD/dojo.hitch):
dojo.hitch({}, function(){return arguments}, 1, 2)(3, 4); // [1,2,3,4]
This article from 2004 gives a glimpse into some of the rationale behind
Prototype's bind (IIRC, it is that paper that influenced Prototype to
include bind
) — www.brockman.se/2004/method-references
It also shows an example of why adding support for partial application to
bind
could be useful.
On 1 May 2010 11:16, Jürg Lehni <lists at scratchdisk.com> wrote:
Also, could you show me a real world use case of the argument concatenation feature found in hitch and bind? I am not working with Prototype nor Dojo, but so far I have not come to face a situation where this would have been useful. The resulting code seems rather confusing to me, very hard for someone else than the writer to figure out what is actually going on.
Coming from languages where 'this' is easily thought of as an implicit first function argument, bind() starts to feel more elegant. It has similar counterparts in Python (functools.partial()), C++ (e.g. std::bind<>, boost::bind<>), perl (sub::curry), and so on.
Two major advantages to pre-pending arguments are to specialize an existing function or algorithm for a particular use case (e.g. to make a generic function conform to a particular interface for some library/API, or e.g. given some complex sorting function, currying its initial arguments which are parameters that control its behaviour, leaving the last 2 for Array.prototype.sort() interface), and as an ad-hoc means of passing arbitrary data between continuations without requiring more than an argument list specification as the 'contract' between either side (say, instead of an additional object whose property names and types are agreed on and specified in some way).
There are many more real-world use cases for partial function application, although perhaps they're a little hackier, e.g. having a particular function-with-argument-list be called in response to a DOM event, by pre-populating all its expected arguments and having it simply ignore the extraneous Event object. This could either be a single line of code with bind(), or a more verbose anonymous function covering 1-5 or more lines of code depending on your style.
Another reason for supporting this in the language is that it is currently expensive (in SpiderMonkey and JavaScriptCore at least) to clone and modify an argument list from within JavaScript; removing partial function application is a great way to speed up loops. It would be nice if native code had the chance to intervene instead, because this functionality is not going away from the libraries any time soon, nor is the demand from users.
On Sat, May 1, 2010 at 3:16 AM, Jürg Lehni <lists at scratchdisk.com> wrote:
Also, could you show me a real world use case of the argument concatenation feature found in hitch and bind? I am not working with Prototype nor Dojo, but so far I have not come to face a situation where this would have been useful. The resulting code seems rather confusing to me, very hard for someone else than the writer to figure out what is actually going on.
I agree that all of the following cases do not by themselves represent a good reason to make bind() official. Rather, you could consider them more like "stupid bind() tricks."
code.google.com/p/es-lab/source/browse/trunk/src/jsonMLWalkers.js#66
Also lines 88 and 128 make the same point.
- Because ES3 has no "...", Cajita currently needs the triangle of hackery found at code.google.com/p/google-caja/source/browse/trunk/src/com/google/caja/cajita.js#3801
Even though ES5 still has no "...", we can still replace the triangle of hackery with:
function construct(ctor, args) {
ctor = asCtor(ctor); // if you don't know Caja, don't worry about this
line var boundCtor = Function.prototype.bind.apply(ctor, args); return new boundCtor(); }
Note: since no one has implemented primitive bind() yet, the above code is completely untested.
Mark S. Miller wrote:
As an alternative to saying "the original bindings of..." in the spec, we could provide a way to actually get the original bindings in ECMAScript code. This is independently useful, e.g. for secure subset run-times. For instance:
Function.prototype.bind = function(self, var_args) { var thisFunc = this; var leftArgs = original.Array.slice(arguments, 1); return function(var_args) { var args = leftArgs.concat(original.Array.slice(arguments, 0)); return original.Function.apply(thisFunc, self, args); } }
where 'original' is a non-Writable property of the global object that refers to a sealed object, which in turn refers to sealed copies of the primordial constructors.
(If there were a "run in this scope with no other bindings" primitive, the 'original' object could then be used with that primitive to run code in an environment that provides no authority.)
I have also assumed the existence of a "static" Function.apply which takes the function to be applied as an explicit first argument. This is necessary because otherwise there is no way to bind 'this' correctly given that thisFunc.apply may have been overridden (or at least, I don't know of any way to do it).