Coercing 'this' (was: Topic list - pending changes and issues for the ES3.1 spec)
On Tue, Sep 9, 2008 at 2:11 PM, Mark S. Miller <erights at google.com> wrote:
On Tue, Sep 9, 2008 at 9:21 AM, Mark S. Miller <erights at google.com> wrote:
What should be the rules for coercing non-object 'this' values?
In a previous thread we've already settled that ES3.1-strict will not coerce null or undefined 'this' values. In order to do this, we're going to migrate the spec language for coercing a null-or-undefined 'this' from the caller-side that scattered all over the spec (where it's hard to make it conditional on strictness), to the callee side in section 11.1.1. For non-strict code, this should make no observable difference.
Some open questions:
The ES3 spec language uses null as the implicit 'this' value associated with calling a function as a function. However, since the current spec language also coerces both null and undefined to the global object, it is unobservable whether null or undefined is used as the implicit 'this' value. In ES3.1 strict this difference becomes observable. In the interests of explaining 'this'-binding as being more like parameter-binding, I would like to see this changed to undefined. Calling a function as a function is like calling it without an argument for its 'this' parameter. I think this is more intuitive.
When a primitive non-object type (number, boolean, string, presumably decimal) is passed as a 'this' value, the ES3 spec coerces it to a new corresponding wrapper object each time. This fresh allocation is observable and therefore expensive. For example, let's say someone adds a new method to String.prototype:
String.prototype.capture = function() { return this; }; var foo = "foo"; foo.capture() === foo.capture() false
The expense of this allocation imposes a cost on all programs in order to provide a semantics that's strictly worse than not providing it, and which hardly any programs will ever care about anyway. To avoid the extra expense within the current semantics, an implementation would have to do escape analysis. IIRC, the ES4 spec avoids requiring this allocation, as that was felt to be an incompatibility everyone could live with. I agree. I propose that primitive 'this' values not be coerced at all.
Let's posit for the sake of discussion that a primitive decimal type is in and we proceed as you describe above. Furthermore, and just for the sake of simplicity as it isn't really pivotal, let's assume that all named Decimal methods are "static", so there is no instance methods of interest.
In such a scenario, is there value in providing a Decimal wrapper type at all? Could new Decimal('5') simply throw an exception and a footnote be placed someplace in the spec that ECMA TC39 reserves the right to change this behavior in future revs of the spec?
I'm not asking because I consider a Decimal wrapper is hard to implement, but because I don't understand what value such a wrapper would provide other than to be available for the situation you describe above.
If primitive 'this' values are no longer coerced, we can still explain the semantics of property lookup of an expression like 'foo.capture()' by saying that the property is looked up in the wrapper's prototype. Or we could say that the property is looked up after wrapping, but a method call proceeds with the original unwrapped value. In other words
"foo".capture()
should be equivalent to
Object("foo").capture.call("foo")
given the original bindings for Object and Function.call.
It occurs to me that the latter case (looking up after wrapping) trades a single wrapping at function entry against potentially multiple wrappings that may occur inside the function.
-- Cheers, --MarkM
- Sam Ruby
On Wed, Sep 10, 2008 at 3:07 AM, Sam Ruby <rubys at intertwingly.net> wrote:
Let's posit for the sake of discussion that a primitive decimal type is in and we proceed as you describe above. Furthermore, and just for the sake of simplicity as it isn't really pivotal, let's assume that all named Decimal methods are "static", so there is no instance methods of interest.
In such a scenario, is there value in providing a Decimal wrapper type at all? Could new Decimal('5') simply throw an exception and a footnote be placed someplace in the spec that ECMA TC39 reserves the right to change this behavior in future revs of the spec?
I'm not asking because I consider a Decimal wrapper is hard to implement, but because I don't understand what value such a wrapper would provide other than to be available for the situation you describe above.
Symmetry. ES3 has certain regularities. As unpleasant as its choice of symmetries are -- there should never have been separate primitives and wrappers -- we don't get to remove them. Generic value-handling code benefits by assuming these symmetries. Currently, for all primitive values v,
typeof v !== 'object' typeof Object(v) === 'object' Object(v) !== Object(v) Object(v).valueOf() === v
Let's not break these.
If primitive 'this' values are no longer coerced, we can still explain the semantics of property lookup of an expression like 'foo.capture()' by saying that the property is looked up in the wrapper's prototype. Or we could say that the property is looked up after wrapping, but a method call proceeds with the original unwrapped value. In other words
"foo".capture()
should be equivalent to
Object("foo").capture.call("foo")
given the original bindings for Object and Function.call.
It occurs to me that the latter case (looking up after wrapping) trades a single wrapping at function entry against potentially multiple wrappings that may occur inside the function.
If we adopt the no-this-coercion rule and use wrapping only to explain the lookup, then the implied allocation above is unobservable and can easily be optimized away. By contrast, the allocation implied by the current spec can only sometimes be optimized away, and only by doing an escape analysis.
Mark S. Miller wrote:
On Wed, Sep 10, 2008 at 3:07 AM, Sam Ruby <rubys at intertwingly.net> wrote: [...]
I'm not asking because I consider a Decimal wrapper is hard to implement, but because I don't understand what value such a wrapper would provide other than to be available for the situation you describe above.
Symmetry. ES3 has certain regularities. As unpleasant as its choice of symmetries are -- there should never have been separate primitives and wrappers -- we don't get to remove them. Generic value-handling code benefits by assuming these symmetries. Currently, for all primitive values v,
typeof v !== 'object' typeof Object(v) === 'object' Object(v) !== Object(v) Object(v).valueOf() === v
Let's not break these.
Why does 'Object(v)' need to be supported for decimal values? Just make it throw; then, the above invariants will hold whenever they don't throw.
I don't see that there's any reason to compound the mistake of having object wrappers by repeating this mistake for each new primitive type. ECMAScript is not Java: "generic value-handling code" does not need to use the wrapper objects, because the primitive values can be held in a variable of the same type (i.e. of the single dynamic type) as any object. The wrapper objects are best ignored; for most purposes you can simply treat their methods as if they were defined on the primitive values, as they should have been.
On 2008-09-10, at 13:36EDT, Mark S. Miller wrote:
Or we could say that the property is looked up after wrapping, but a method call proceeds with the original unwrapped value. In other
words"foo".capture()
should be equivalent to
Object("foo").capture.call("foo")
given the original bindings for Object and Function.call.
It occurs to me that the latter case (looking up after wrapping) trades a single wrapping at function entry against potentially multiple wrappings that may occur inside the function.
If we adopt the no-this-coercion rule and use wrapping only to explain the lookup, then the implied allocation above is unobservable and can easily be optimized away. By contrast, the allocation implied by the current spec can only sometimes be optimized away, and only by doing an escape analysis.
Will it be a problem that then this
might sometimes not be an Object
in a function? If not, this is a clever refinement.
On Sep 9, 2008, at 11:11 AM, Mark S. Miller wrote:
On Tue, Sep 9, 2008 at 9:21 AM, Mark S. Miller <erights at google.com>
wrote:What should be the rules for coercing non-object 'this' values?
In a previous thread we've already settled that ES3.1-strict will not coerce null or undefined 'this' values. In order to do this, we're going to migrate the spec language for coercing a null-or-undefined 'this' from the caller-side that scattered all over the spec (where it's hard to make it conditional on strictness), to the callee side in section 11.1.1.
Great, this improves the spec.
For non-strict code, this should make no observable difference.
Some open questions:
The ES3 spec language uses null as the implicit 'this' value associated with calling a function as a function. However, since the current spec language also coerces both null and undefined to the global object, it is unobservable whether null or undefined is used as the implicit 'this' value. In ES3.1 strict this difference becomes observable. In the interests of explaining 'this'-binding as being more like parameter-binding, I would like to see this changed to undefined. Calling a function as a function is like calling it without an argument for its 'this' parameter. I think this is more intuitive.
This is strictly more useful than the current mess, and David-Sarah
made good use of it in his Date self-hosting for the Date constructor
function, so it can tell when it's called as a function. But it's
still not enough in general:
var funnyObj = {method: Date} funnyObj.method(2008,9,11)
would bind funnyObj to |this| in Date, which would flow into the
constructor-case code as the new Date object to initialize.
This is also badly incompatible. Global functions are used as methods
of their window objects. Calling them via this.func() or
otherFrame.func() works, but so do calls to func() from the same window.
When a primitive non-object type (number, boolean, string, presumably decimal) is passed as a 'this' value, the ES3 spec coerces it to a new corresponding wrapper object each time. This fresh allocation is observable and therefore expensive.
This has been covered in past threads here, and in ES4 work. The
relevant quote from
www.ecmascript.org/es4/spec/incompatibilities.pdf
is this:
The wrapping of primitives on calls to methods that do not have bound-
this types might at first glance
appear to reintroduce the very problem we were trying to avoid, but
this is not so. The predefined String,
Number, and Boolean prototype methods in ES4 all have bound-this
types (the types are Object,
AnyNumber, and AnyBoolean, respectively), so no wrapping occurs on
calls to predefined methods – this
optimization is also performed in some existing ES3 implementations.
Furthermore, wrapping can be
performed lazily; it does not have to occur until a method references
its this value.
This is what some implementations in fact do. For example,
SpiderMonkey passes primitive |this| to native functions that
announce they're ready for this (relatively recent, in the long
history of the API) change to the type of |this|, and we have a bug
open to do likewise for scripted functions, with lazy wrapping as
needed.
IIRC, the ES4 spec avoids requiring this allocation, as that was felt to be an incompatibility everyone could live with.
No, see above. ES4 (and real implementations that use primitive types
as ES4 proposed here) does not break compatibility here. The "built-
in" functions that are methods of the primitives work because
string.prototype === String.prototype, e.g. -- no string wrapped in
new String required for |this| when calling such prototype-delegated
methods. Scripted functions added to String.prototype do require
wrapping, but as the doc notes it can be done lazily.
The trac ticket
bugs.ecmascript.org/ticket/281
notes that lazy wrapping must preserve identity.
I agree. I propose that primitive 'this' values not be coerced at all.
This is a pretty big incompatibility. I'll scan some Ajax libraries
to look for obvious breaks. I suspect they'll be easy to find.
If primitive 'this' values are no longer coerced, we can still explain the semantics of property lookup of an expression like 'foo.capture()' by saying that the property is looked up in the wrapper's prototype.
ES4 did this by uniting string.prototype and String.prototype, etc.
Or we could say that the property is looked up after wrapping, but a method call proceeds with the original unwrapped value. In other words
"foo".capture()
should be equivalent to
Object("foo").capture.call("foo")
given the original bindings for Object and Function.call.
This much is compatible, provided the right wrapper is used (the one
from the current execution context's scope chain's final object),
since there is no string constructor or string.prototype.
On Sep 10, 2008, at 3:07 AM, Sam Ruby wrote:
In such a scenario, is there value in providing a Decimal wrapper type at all?
So people can add methods that we did not think of to
Decimal.prototype. But you assumed static methods only, so perhaps
you meant to exclude this use-case.
Extending standard constructor prototypes is frowned upon currently
because such user-defined prototype properties are enumerable, but
Object.defineProperty/ies stand ready. And people do use this use-
case today, whether we like it or not.
It occurs to me that the latter case (looking up after wrapping) trades a single wrapping at function entry against potentially multiple wrappings that may occur inside the function.
Wrapping must be done at most once, lazily on first |this| use if
possible but at most once only, per |this| parameter binding.
On Thu, Sep 11, 2008 at 11:11 AM, Brendan Eich <brendan at mozilla.org> wrote:
On Sep 9, 2008, at 11:11 AM, Mark S. Miller wrote:
On Tue, Sep 9, 2008 at 9:21 AM, Mark S. Miller <erights at google.com> wrote:
What should be the rules for coercing non-object 'this' values?
In a previous thread we've already settled that ES3.1-strict will not coerce null or undefined 'this' values. In order to do this, we're going to migrate the spec language for coercing a null-or-undefined 'this' from the caller-side that scattered all over the spec (where it's hard to make it conditional on strictness), to the callee side in section 11.1.1.
Great, this improves the spec.
Good. We seem to have no disagreement on this point.
For non-strict code, this should make no observable difference.
Some open questions:
The ES3 spec language uses null as the implicit 'this' value associated with calling a function as a function. However, since the current spec language also coerces both null and undefined to the global object, it is unobservable whether null or undefined is used as the implicit 'this' value. In ES3.1 strict this difference becomes observable. In the interests of explaining 'this'-binding as being more like parameter-binding, I would like to see this changed to undefined. Calling a function as a function is like calling it without an argument for its 'this' parameter. I think this is more intuitive.
This is strictly more useful than the current mess, and David-Sarah made good use of it in his Date self-hosting for the Date constructor function, so it can tell when it's called as a function. But it's still not enough in general:
var funnyObj = {method: Date} funnyObj.method(2008,9,11)
would bind funnyObj to |this| in Date, which would flow into the constructor-case code as the new Date object to initialize.
Agreed that thsi proposal does not prevent this existing confusion. Neither does it make it worse. If there's something we can plausibly do to fix this, I'd love to! Any suggestions?
This is also badly incompatible. Global functions are used as methods of their window objects. Calling them via this.func() or otherFrame.func() works, but so do calls to func() from the same window.
Since there's only one window per JS global object (obviously, since they're the same object), all the methods of the window could already be bound to their window, and so not care what their 'this' is. However, that would break code such as 'window1.func.call(window2)'. Currently, I expect func will operate on window2; but if func is already bound to window1 it will still operate on window1. Is this change of behavior a problem?
When a primitive non-object type (number, boolean, string, presumably decimal) is passed as a 'this' value, the ES3 spec coerces it to a new corresponding wrapper object each time. This fresh allocation is observable and therefore expensive.
This has been covered in past threads here, and in ES4 work. The relevant quote from
www.ecmascript.org/es4/spec/incompatibilities.pdf
is this:
The wrapping of primitives on calls to methods that do not have bound-this types might at first glance appear to reintroduce the very problem we were trying to avoid, but this is not so. The predefined String, Number, and Boolean prototype methods in ES4 all have bound-this types (the types are Object, AnyNumber, and AnyBoolean, respectively), so no wrapping occurs on calls to predefined methods – this optimization is also performed in some existing ES3 implementations. Furthermore, wrapping can be performed lazily; it does not have to occur until a method references its this value.
This is what some implementations in fact do. For example, SpiderMonkey passes primitive |this| to native functions that announce they're ready for this (relatively recent, in the long history of the API) change to the type of |this|, and we have a bug open to do likewise for scripted functions, with lazy wrapping as needed.
IIRC, the ES4 spec avoids requiring this allocation, as that was felt to be an incompatibility everyone could live with.
No, see above. ES4 (and real implementations that use primitive types as ES4 proposed here) does not break compatibility here. The "built-in" functions that are methods of the primitives work because string.prototype === String.prototype, e.g. -- no string wrapped in new String required for |this| when calling such prototype-delegated methods. Scripted functions added to String.prototype do require wrapping, but as the doc notes it can be done lazily.
The trac ticket
bugs.ecmascript.org/ticket/281
notes that lazy wrapping must preserve identity.
I agree. I propose that primitive 'this' values not be coerced at all.
This is a pretty big incompatibility. I'll scan some Ajax libraries to look for obvious breaks. I suspect they'll be easy to find.
Any results?
If the answer is that we can't avoid wrapping unconditionally, then I propose that we wrap only on entry to non-strict functions, where this wrapping is again cetralized in the spec to section 11.1.1. Elsewhere you've written "more carrots, less sticks", with which I heartily agree. If "strict" suppresses non-stratified reflection that prevents many useful optimizations (with, delete <var>, arguments.caller, Function.caller,
Function.arguments) and removes pointless and costly wrapping, then the common wisdom may become "Make your program work under strict mode so it'll be faster."
If primitive 'this' values are no longer coerced, we can still explain the semantics of property lookup of an expression like 'foo.capture()' by saying that the property is looked up in the wrapper's prototype.
ES4 did this by uniting string.prototype and String.prototype, etc.
Or we could say that the property is looked up after wrapping, but a method call proceeds with the original unwrapped value. In other words
"foo".capture()
should be equivalent to
Object("foo").capture.call("foo")
given the original bindings for Object and Function.call.
This much is compatible, provided the right wrapper is used (the one from the current execution context's scope chain's final object), since there is no string constructor or string.prototype.
Yes. After ES3.1, I'd like to shift to your way of explaining it, in terms of all values being objects. If we do this right, this shift should only be a better way of explaining the same behavior.
On Sep 16, 2008, at 1:54 AM, Mark S. Miller wrote:
var funnyObj = {method: Date} funnyObj.method(2008,9,11)
would bind funnyObj to |this| in Date, which would flow into the
constructor-case code as the new Date object to initialize.Agreed that thsi proposal does not prevent this existing confusion.
Neither does it make it worse. If there's something we can
plausibly do to fix this, I'd love to! Any suggestions?
Not right now. Such a self-hosted Date could use instanceof to test a
|this| that was not undefined, and construct only if |this instanceof
Date| -- but it still act as a re-initializer of called on an
existing Date instance, e.g. d = new Date(2008,9,1); Date(d) -- still
not per-spec, but this is a problem for later.
This is also badly incompatible. Global functions are used as
methods of their window objects. Calling them via this.func() or
otherFrame.func() works, but so do calls to func() from the same
window.Since there's only one window per JS global object (obviously,
since they're the same object), all the methods of the window could
already be bound to their window,
You're proposing to bind |this| to all window functions in strict mode?
and so not care what their 'this' is. However, that would break
code such as 'window1.func.call(window2)'. Currently, I expect func
will operate on window2; but if func is already bound to window1 it
will still operate on window1. Is this change of behavior a problem?
It could be; if you're talking about strict mode, it is yet another
migration tax hike, and a bit of an unwarranted extra complexity to
strict mode. What other this-binding does strict mode imply? I don't
remember anything like this.
I agree. I propose that primitive 'this' values not be coerced at all.
This is a pretty big incompatibility. I'll scan some Ajax libraries
to look for obvious breaks. I suspect they'll be easy to find.Any results?
No, I'm traveling and short on time and access. Maybe some of the
Prototype, Dojo, MochiKit, or JQuery folks on the list could help me
out and comment.
If the answer is that we can't avoid wrapping unconditionally, then
I propose that we wrap only on entry to non-strict functions, where
this wrapping is again cetralized in the spec to section 11.1.1.
Elsewhere you've written "more carrots, less sticks", with which I
heartily agree. If "strict" suppresses non-stratified reflection
that prevents many useful optimizations (with, delete <var>
(what about delete <var>? That it returns false should not prevent
optimizations...)
, arguments.caller, Function.caller, Function.arguments) and
removes pointless and costly wrapping,
Implements avoid wrapping, but sure: at some cost to implementation
complexity. Relieving that cost in strict mode does not remove the
complexity, though -- the default mode still requires (only for
interpreted functions, albeit lazy if the function mentions |this| or
|eval|) primitive-this wrapping.
then the common wisdom may become "Make your program work under
strict mode so it'll be faster."
Quality implementations won't get any faster.
I'm not sure what we are saving here. The point of strict mode is to
prevent bad practices and pitfalls, but adding scripted functions as
methods of standard prototypes, or otherwise arranging to call
methods on primitives, is not bad practice in my book. It's good
enough for s.charAt(i), why not for s.myCustomStringMethod()? One of
the use-cases for the new 3.1 Object methods is binding non-
enumerable prototype properties.
Yes. After ES3.1, I'd like to shift to your way of explaining it,
in terms of all values being objects. If we do this right, this
shift should only be a better way of explaining the same behavior.
The wrapping of primitive-this was still seen as required (optimized
to be lazy) for compatibility, even when ES4 had string, double,
boolean, etc value types in ES4. Breaking compatibility in ES3.1 and
future strict modes on this point seems independent of how one models
primitive types. Unless I'm missing something, we're only concerned
with whether to preserve primitive |this|, breaking compatibility,
when in strict mode; or else to wrap as in loose mode.
The spec has to wrap in loose mode. Implementations have to wrap
(lazily, if |this| is used, etc.) in loose mode. Implementations are
going crazy-fast these days and they avoid unnecessary wrapping, or
will shortly. What's the reason apart from "it'll be faster" to
change primitive |this| handling in strict mode, at a cost in spec
and implementation complexity?
Mark S. Miller wrote:
On Thu, Sep 11, 2008 at 11:11 AM, Brendan Eich <brendan at mozilla.org> wrote:
The ES3 spec language uses null as the implicit 'this' value associated with calling a function as a function. However, since the current spec language also coerces both null and undefined to the global object, it is unobservable whether null or undefined is used as the implicit 'this' value. In ES3.1 strict this difference becomes observable. In the interests of explaining 'this'-binding as being more like parameter-binding, I would like to see this changed to undefined. Calling a function as a function is like calling it without an argument for its 'this' parameter. I think this is more intuitive.
This is strictly more useful than the current mess, and David-Sarah made good use of it in his Date self-hosting for the Date constructor function, so it can tell when it's called as a function. But it's still not enough in general:
var funnyObj = {method: Date} funnyObj.method(2008,9,11)
would bind funnyObj to |this| in Date, which would flow into the constructor-case code as the new Date object to initialize.
This turns out to be a potential security bug in Jacaranda draft-0.3 (www.jacaranda.org/jacaranda-spec-0.3.txt).
Normally functions that refer directly to 'this' are not first-class in Jacaranda. The bug is that Array, Boolean, Date, Number, RegExp, String, and *Error are functions that refer to 'this', and they are first-class expressions.
In general a constructor can either return a new constructed object, or initialize the 'this' object "in-place". I've only done limited testing, but it appears that the implementations of the global constructors in Firefox 3 and IE7 return new constructed objects, which means that the bug would not have been exploitable in those browsers. In any case, I will close off this line of attack in Jacaranda draft-0.4.
The fix is easy -- make references to these constructors second-class expressions. For those who have read the Jacaranda spec and are interested in the detail: change the definition of expClass in the id::unreservedIdentifier case of <PrimaryExpression> from
expClass = (id is <exposedConstant>) ? 0 :
(id.text == 'arguments') ? 4 : 1 }
to
expClass = (id is <exposedConstant>) ? 0 :
(id is <secondClassConstructor>) ? 2 :
(id.text == 'arguments') ? 4 : 1 }
and add the following production:
<secondClassConstructor> : one of Array Boolean Date Number RegExp String Error EvalError SyntaxError TypeError RangeError ReferenceError URIError
(No other constructors that refer to 'this' are directly accessible in Jacaranda.)
Agreed that this proposal does not prevent this existing confusion. Neither does it make it worse. If there's something we can plausibly do to fix this, I'd love to! Any suggestions?
The problem here is the lack of any run-time type distinction between constructors and other functions. I don't see how to fix the existing confusion in a compatible way. However, when we add the class feature in ES-Harmony, we should avoid making it worse by using a desugaring that allows the class name to be used with 'new', but prevents any use as a function from breaking object encapsulation.
[...]
If "strict" suppresses non-stratified reflection that prevents many useful optimizations (with, delete <var>, arguments.caller, Function.caller, Function.arguments) and removes pointless and costly wrapping, then the common wisdom may become "Make your program work under strict mode so it'll be faster."
That would certainly be nice.
On Mon, Sep 15, 2008 at 6:16 PM, Brendan Eich <brendan at mozilla.org> wrote:
On Sep 16, 2008, at 1:54 AM, Mark S. Miller wrote:
This is also badly incompatible. Global functions are used as methods of
their window objects. Calling them via this.func() or otherFrame.func() works, but so do calls to func() from the same window.
Since there's only one window per JS global object (obviously, since they're the same object), all the methods of the window could already be bound to their window,
You're proposing to bind |this| to all window functions in strict mode?
Worse. Since strict mode is only per program-unit, not per global object (frame), it makes no sense for such binding to be specific to strict mode. Rather, I'm proposing/asking whether it would be noticeably incompatible if all browser-provided window functions were bound to their window, period. Note that this need not be implemented or specified in terms of a specific 'bind' operation. It suffices to define all the browser-provided widow functions to refer to their window as 'window' rather than as 'this'.
For scripted functions, it would depend on the strictness of the function. A non-strict scripted function would still coerce the undefined 'this' to its window, and so would see no difference.
and so not care what their 'this' is. However, that would break code such as 'window1.func.call(window2)'. Currently, I expect func will operate on window2; but if func is already bound to window1 it will still operate on window1. Is this change of behavior a problem?
It could be; if you're talking about strict mode, it is yet another migration tax hike, and a bit of an unwarranted extra complexity to strict mode. What other this-binding does strict mode imply? I don't remember anything like this.
I think I see how we're misunderstanding each other. I am not proposing that any bind-to-window happen by magic, nor any other radical changes to this-binding from what we talked about. I was merely suggesting that browser-provided window functions, in order to preserve their current behavior when called as a function, lexically capture their window object as 'window' rather than relying on 'this' binding.
However, now that I write this, I realize we can simply explain their current behavior by saying that they are (effectively) non-strict functions. If this was your real point all along, sorry for the noise.
I agree. I propose that primitive 'this' values not
be coerced at all.
This is a pretty big incompatibility. I'll scan some Ajax libraries to look for obvious breaks. I suspect they'll be easy to find.
Any results?
No, I'm traveling and short on time and access. Maybe some of the Prototype, Dojo, MochiKit, or JQuery folks on the list could help me out and comment.
Please. And YUI please. Tobie has made sufficient progress getting Prototype working under Caja that I'm fairly confident it's ok with Prototype.
If the answer is that we can't avoid wrapping unconditionally, then I propose that we wrap only on entry to non-strict functions, where this wrapping is again cetralized in the spec to section 11.1.1. Elsewhere you've written "more carrots, less sticks", with which I heartily agree. If "strict" suppresses non-stratified reflection that prevents many useful optimizations (with, delete <var>
(what about delete <var>? That it returns false should not prevent optimizations...)
Is this really better than a static rejection? Who gains by postponing this failure?
, arguments.caller, Function.caller, Function.arguments) and removes pointless and costly wrapping,
Implements avoid wrapping, but sure: at some cost to implementation complexity. Relieving that cost in strict mode does not remove the complexity, though -- the default mode still requires (only for interpreted functions, albeit lazy if the function mentions |this| or |eval|) primitive-this wrapping.
then the common wisdom may become "Make your program work under strict mode so it'll be faster."
Quality implementations won't get any faster.
Given that any lazy allocation has to preserve identity, as you point out, when you can't show that 'this' doesn't escape, that preservation requires extra bookkeeping, even if the allocation is never actually needed.
I'm not sure what we are saving here. The point of strict mode is to prevent bad practices and pitfalls, but adding scripted functions as methods of standard prototypes, or otherwise arranging to call methods on primitives, is not bad practice in my book. It's good enough for s.charAt(i), why not for s.myCustomStringMethod()? One of the use-cases for the new 3.1 Object methods is binding non-enumerable prototype properties.
I'm not sure, but perhaps we're agreeing. The benefits of avoiding the pointless wrapping of primitive 'this'es would be greatest for strict methods added to String.prototype. Why should they pay for wrapping that just makes their life harder anyway?
Yes. After ES3.1, I'd like to shift to your way of explaining it, in terms of all values being objects. If we do this right, this shift should only be a better way of explaining the same behavior.
The wrapping of primitive-this was still seen as required (optimized to be lazy) for compatibility, even when ES4 had string, double, boolean, etc value types in ES4. Breaking compatibility in ES3.1 and future strict modes on this point seems independent of how one models primitive types.
Yes, the two issues are independent. We were just discussing both. No need to couple them.
Unless I'm missing something, we're only concerned with whether to preserve primitive |this|, breaking compatibility, when in strict mode; or else to wrap as in loose mode.
Yes, that's the question. If we continue to explain the behavior of browser provided window methods as non-strict functions, then there's no issue there either. If a future browser spec wishes to explain their behavior as if they are strict functions, then there's a possibly-minor issue, but that's outside the scope of an ES spec.
The spec has to wrap in loose mode. Implementations have to wrap (lazily, if |this| is used, etc.) in loose mode. Implementations are going crazy-fast these days and they avoid unnecessary wrapping, or will shortly. What's the reason apart from "it'll be faster" to change primitive |this| handling in strict mode, at a cost in spec and implementation complexity?
It makes the semantics of strict mode simpler, easier to explain, less
accident-prone, and less irregular. Strict mode is our one chance to shed some of the burdens of our previous mistakes.
On Mon, Sep 15, 2008 at 7:28 PM, David-Sarah Hopwood < david.hopwood at industrial-designers.co.uk> wrote:
Agreed that this proposal does not prevent this existing confusion. Neither does it make it worse. If there's something we can plausibly do to fix this, I'd love to! Any suggestions?
The problem here is the lack of any run-time type distinction between constructors and other functions. I don't see how to fix the existing confusion in a compatible way. However, when we add the class feature in ES-Harmony, we should avoid making it worse by using a desugaring that allows the class name to be used with 'new', but prevents any use as a function from breaking object encapsulation.
The desugaring I presented in "Look ma, no 'this'" does so, precisely because it avoids 'this'.
On Sep 15, 2008, at 7:51 PM, Mark S. Miller wrote:
On Mon, Sep 15, 2008 at 6:16 PM, Brendan Eich <brendan at mozilla.org>
wrote: On Sep 16, 2008, at 1:54 AM, Mark S. Miller wrote:This is also badly incompatible. Global functions are used as
methods of their window objects. Calling them via this.func() or
otherFrame.func() works, but so do calls to func() from the same
window.Since there's only one window per JS global object (obviously,
since they're the same object), all the methods of the window could
already be bound to their window,You're proposing to bind |this| to all window functions in strict
mode?Worse. Since strict mode is only per program-unit, not per global
object (frame), it makes no sense for such binding to be specific to
strict mode. Rather, I'm proposing/asking whether it would be
noticeably incompatible if all browser-provided window functions
were bound to their window, period. Note that this need not be
implemented or specified in terms of a specific 'bind' operation. It
suffices to define all the browser-provided widow functions to refer
to their window as 'window' rather than as 'this'.
That would not be compatible with current Safari/WebKit behavior, and
it may be a compatibility risk. I think anyone proposing this change
should do research to determine the scope of the risk, and our default
position should be "no compatibility-breaking changes until proven
relatively safe".
, Maciej
On Mon, Sep 15, 2008 at 8:05 PM, Maciej Stachowiak <mjs at apple.com> wrote:
On Sep 15, 2008, at 7:51 PM, Mark S. Miller wrote:
On Mon, Sep 15, 2008 at 6:16 PM, Brendan Eich <brendan at mozilla.org> wrote:
On Sep 16, 2008, at 1:54 AM, Mark S. Miller wrote:
This is also badly incompatible. Global functions are used as methods of
their window objects. Calling them via this.func() or otherFrame.func() works, but so do calls to func() from the same window.
Since there's only one window per JS global object (obviously, since they're the same object), all the methods of the window could already be bound to their window,
You're proposing to bind |this| to all window functions in strict mode?
Worse. Since strict mode is only per program-unit, not per global object (frame), it makes no sense for such binding to be specific to strict mode. Rather, I'm proposing/asking whether it would be noticeably incompatible if all browser-provided window functions were bound to their window, period. Note that this need not be implemented or specified in terms of a specific 'bind' operation. It suffices to define all the browser-provided widow functions to refer to their window as 'window' rather than as 'this'.
That would not be compatible with current Safari/WebKit behavior, and it may be a compatibility risk. I think anyone proposing this change should do research to determine the scope of the risk, and our default position should be "no compatibility-breaking changes until proven relatively safe".
Agreed. Probing such questions on this list is an economical first step in any such research. I withdraw the suggestion, seeing as how it is outside the jurisdiction of ES anyway.
On Tue, Sep 9, 2008 at 9:21 AM, Mark S. Miller <erights at google.com> wrote:
In a previous thread we've already settled that ES3.1-strict will not coerce null or undefined 'this' values. In order to do this, we're going to migrate the spec language for coercing a null-or-undefined 'this' from the caller-side that scattered all over the spec (where it's hard to make it conditional on strictness), to the callee side in section 11.1.1. For non-strict code, this should make no observable difference.
Some open questions:
The ES3 spec language uses null as the implicit 'this' value associated with calling a function as a function. However, since the current spec language also coerces both null and undefined to the global object, it is unobservable whether null or undefined is used as the implicit 'this' value. In ES3.1 strict this difference becomes observable. In the interests of explaining 'this'-binding as being more like parameter-binding, I would like to see this changed to undefined. Calling a function as a function is like calling it without an argument for its 'this' parameter. I think this is more intuitive.
When a primitive non-object type (number, boolean, string, presumably decimal) is passed as a 'this' value, the ES3 spec coerces it to a new corresponding wrapper object each time. This fresh allocation is observable and therefore expensive. For example, let's say someone adds a new method to String.prototype:
The expense of this allocation imposes a cost on all programs in order to provide a semantics that's strictly worse than not providing it, and which hardly any programs will ever care about anyway. To avoid the extra expense within the current semantics, an implementation would have to do escape analysis. IIRC, the ES4 spec avoids requiring this allocation, as that was felt to be an incompatibility everyone could live with. I agree. I propose that primitive 'this' values not be coerced at all.
If primitive 'this' values are no longer coerced, we can still explain the semantics of property lookup of an expression like 'foo.capture()' by saying that the property is looked up in the wrapper's prototype. Or we could say that the property is looked up after wrapping, but a method call proceeds with the original unwrapped value. In other words
should be equivalent to
given the original bindings for Object and Function.call.