Function.prototype.bind

Brendan Eich brendan at mozilla.org
Thu Sep 11 03:18:08 PDT 2008


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:

1. Web JS is often user-agent forked into the IE path and the  
everything-else/Mozilla path.

2. 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:

http://wiki.ecmascript.org/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.


>> https://bugzilla.mozilla.org/show_bug.cgi?id=412324 (Error)

You ignored this one. Why?


>> http://stj.msn.com/br/gbl/js/3/mozcompat.js
>
> 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:

https://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; https://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:
>
> 1. 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.


> 2. simplifying the expansions of syntactic sugar;

Making two categories of constructor bindings complexifies.


> 3. 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.

/be

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://mail.mozilla.org/pipermail/es-discuss/attachments/20080911/f8440267/attachment-0001.html 


More information about the Es-discuss mailing list