`this`: methods versus functions
On Nov 9, 2011, at 3:48 PM, Axel Rauschmayer wrote:
Or are there other plans to get it solved? I would still love to see that happen, it’s a remarkably subtle source of errors. Could functions adopt the block-lambda semantics of picking up the
this
of the surrounding scope when not invoked as methods? It seems like that could work in strict mode where no one expectsthis
to have a value.No, if you call such functions via object.method() references then this binds to object. You can't break such compatibility only at runtime, and only some of the time.
Got it. I’m assuming that’s a performance issue?
You could say that. If we inherit by default but it's a "soft binding", then the inner function has to carry that reference with it, but in a way that can be overridden.
We talked about lexical this for functions long ago (Jan. 2008? at Google anyway) and IIRC Mark found a subtler flaw.
On Nov 9, 2011, at 4:00 PM, Brendan Eich wrote:
On Nov 9, 2011, at 3:48 PM, Axel Rauschmayer wrote:
Or are there other plans to get it solved? I would still love to see that happen, it’s a remarkably subtle source of errors. Could functions adopt the block-lambda semantics of picking up the
this
of the surrounding scope when not invoked as methods? It seems like that could work in strict mode where no one expectsthis
to have a value.No, if you call such functions via object.method() references then this binds to object. You can't break such compatibility only at runtime, and only some of the time.
Got it. I’m assuming that’s a performance issue?
You could say that. If we inherit by default but it's a "soft binding", then the inner function has to carry that reference with it, but in a way that can be overridden.
We talked about lexical this for functions long ago (Jan. 2008? at Google anyway) and IIRC Mark found a subtler flaw.
But again, it's a runtime incompatible change, even ignoring performance. Code today may count on this == global in non-strict mode, or this === undefined in strict mode, for inner functions not called as methods.
Making such a runtime-incompatible change uses up one of my "five fingers of fate" and it's not to be done lightly.
On Wed, Nov 9, 2011 at 4:00 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Nov 9, 2011, at 3:48 PM, Axel Rauschmayer wrote:
We talked about lexical this for functions long ago (Jan. 2008? at Google anyway) and IIRC Mark found a subtler flaw.
I think my original example was smaller and more elegant. But the following is adequate to demonstrate the problem:
function Outer(secret) { "use strict";
this.v = secret;
this.w = secret * 2;
this.x = secret * 3;
this.InnerPoint = function(x, y) {
this.x = x;
this.y = y;
};
this.InnerPoint.prototype = {
getX: function() { return this.x; },
getY: function() { return this.y; }
};
}
Alice does: var outer = new Outer(mySecret); var innerPoint = new outer.InnerPoint(3,5); bob(innerPoint); // passed innerPoint to Bob, who Alice does not trust.
Today, Bob, receiving innerPoint, has no way to obtain Alice's secret. Given your proposal, Bob could do
(1,innerPoint.getX)() / 3;
Today, if Bob does that, the getX call fails when it tries to evaluate undefined.x.
Got it. I’m assuming that’s a performance issue?
You could say that. If we inherit by default but it's a "soft binding", then the inner function has to carry that reference with it, but in a way that can be overridden.
We talked about lexical this for functions long ago (Jan. 2008? at Google anyway) and IIRC Mark found a subtler flaw.
But again, it's a runtime incompatible change, even ignoring performance. Code today may count on this == global in non-strict mode, or this === undefined in strict mode, for inner functions not called as methods.
Making such a runtime-incompatible change uses up one of my "five fingers of fate" and it's not to be done lightly.
Agreed. The global object assumption is too prevalent in non-strict mode, so that one is out. Strict mode might still be OK. In any case, if we have both block lambdas and shorter method syntax (*) then everyone will automatically do the right thing in practically all cases. That would be really cool.
(*) I recently heard a story of someone being surprised by seeing the word “function” in object literals – “Isn’t that supposed to be a method? Why is it called a function, then?”
On Nov 9, 2011, at 4:15 PM, Mark S. Miller wrote:
On Wed, Nov 9, 2011 at 4:00 PM, Brendan Eich <brendan at mozilla.com> wrote: On Nov 9, 2011, at 3:48 PM, Axel Rauschmayer wrote:
We talked about lexical this for functions long ago (Jan. 2008? at Google anyway) and IIRC Mark found a subtler flaw.
I think my original example was smaller and more elegant.
I remember longer, but could be misremembering. Anyway, your example here is great,
On 10 November 2011 01:15, Mark S. Miller <erights at google.com> wrote:
On Wed, Nov 9, 2011 at 4:00 PM, Brendan Eich <brendan at mozilla.com> wrote:
We talked about lexical this for functions long ago (Jan. 2008? at Google anyway) and IIRC Mark found a subtler flaw.
I think my original example was smaller and more elegant. But the following is adequate to demonstrate the problem: function Outer(secret) { "use strict"; this.v = secret; this.w = secret * 2; this.x = secret * 3; this.InnerPoint = function(x, y) { this.x = x; this.y = y; }; this.InnerPoint.prototype = { getX: function() { return this.x; }, getY: function() { return this.y; } }; } Alice does: var outer = new Outer(mySecret); var innerPoint = new outer.InnerPoint(3,5); bob(innerPoint); // passed innerPoint to Bob, who Alice does not trust. Today, Bob, receiving innerPoint, has no way to obtain Alice's secret. Given your proposal, Bob could do (1,innerPoint.getX)() / 3; Today, if Bob does that, the getX call fails when it tries to evaluate undefined.x.
I must be missing something here. Are you assuming that "new
outer.InnerPoint(3,4)" would somehow receive outer' as
this' instead
of a fresh object? Why would that be the case with the "soft binding"
described by Axel? Or is this only a counter example for "hard"
lexical binding of `this'?
getX() is designed for a dynamic this
(i.e. this
is an instance of InnerPoint). My proposal would allow an external party to switch to lexical this
(=== function Outer), simply by invoking it as a (non-method) function. I can see how this would be bad.
Clever (took me a moment to figure out what the comma operator does here...). And makes sense: You would not want an invoker to have that kind of power.
If it wasn’t so negative for performance, I probably would apply curryThis() to all of my methods:
var obj = {
method: function (self, arg) { // additional argument self
someFunction(..., function() {
self.otherMethod(arg);
});
}.curryThis(), // introduce an additional argument
otherMethod: function (arg) { ... }
}
Function.prototype.curryThis = function () {
var f = this;
return function () {
var a = Array.prototype.slice.call(arguments);
a.unshift(this);
return f.apply(null, a);
};
};
On 10 November 2011 14:49, Axel Rauschmayer <axel at rauschma.de> wrote:
getX() is designed for a dynamic
this
(i.e.this
is an instance of InnerPoint). My proposal would allow an external party to switch to lexicalthis
(=== function Outer), simply by invoking it as a (non-method) function. I can see how this would be bad.
Ah, OK, never mind. I think I was actually misreading your proposal.
Thanks.
Any suggestion for improvement is highly welcome. But it seems like we can’t do better than a construct that declares statically, how its this
should be passed to it. The cool thing about block lambdas is that you do the right thing without thinking about it. That’s why, if we got arrow syntax instead, I would opt for only having the fat arrow (given that we already have a shorter syntax for methods inside object literals).
I wonder if it made a difference if this
was always stored in an environment (instead of an execution context). Then block lambdas could find them via the scope chain.
On 10 November 2011 15:23, Axel Rauschmayer <axel at rauschma.de> wrote:
I wonder if it made a difference if
this
was always stored in an environment (instead of an execution context). Then block lambdas could find them via the scope chain.
If I understand you correctly, then yes, this is definitely possible
in principle, and in fact corresponds to the standard model of objects
as straightforward records-of-closures (closing over this'). But you could not use prototypes directly anymore, because you would need to close their methods over
this' as well when you construct an object.
IOW, this would require a more class-style approach to inheritance.
Of course, you can use that technique today, by simply never using
this', and instead bind your own
self' explicitly. But obviously,
current engines will make object creation more costly that way.
I wonder if it made a difference if
this
was always stored in an environment (instead of an execution context). Then block lambdas could find them via the scope chain.If I understand you correctly, then yes, this is definitely possible in principle, and in fact corresponds to the standard model of objects as straightforward records-of-closures (closing over
this'). But you could not use prototypes directly anymore, because you would need to close their methods over
this' as well when you construct an object. IOW, this would require a more class-style approach to inheritance.
I don’t understand. Can you give an example? I thought that simply turning this
into a parameter (under the hood, like a hidden first parameter that all functions have) would not change anything:
obj.method(arg1, arg2) => obj.method<obj>(arg1, arg2)
func(arg1, arg2) => func<undefined>(arg1, arg2)
(The hidden parameter is in angle brackets.)
On 10 November 2011 15:58, Axel Rauschmayer <axel at rauschma.de> wrote:
I wonder if it made a difference if
this
was always stored in anenvironment (instead of an execution context). Then block lambdas could find
them via the scope chain.
If I understand you correctly, then yes, this is definitely possible in principle, and in fact corresponds to the standard model of objects as straightforward records-of-closures (closing over
this'). But you could not use prototypes directly anymore, because you would need to close their methods over
this' as well when you construct an object. IOW, this would require a more class-style approach to inheritance.I don’t understand. Can you give an example? I thought that simply turning
this
into a parameter (under the hood, like a hidden first parameter that all functions have) would not change anything:
No, that's how it works right now. The alternative is to lexically close all methods over self at construction time:
function Point(x, y) { var self = this self.x = x self.y = y self.move = function(dx, dy) { self.x += dx; self.dy += dy } }
function ColorPoint(x, y, color) { var self = this Point.call(self, x, y) self.color = color self.recolor = function(c) { self.color = c } }
As said, this doesn't play well with prototype inheritance. You have to put all methods that refer to self on the object itself. But "inner constructors" are straighforward and safe.
Got it. I’m assuming that’s a performance issue?
In principle, one can envision: Bind this lexically by default, override with receiver when called as a function.
var obj1 = { makeFunction: function() { return function () { return this; }; } } var func = obj1. makeFunction(); console.log(func() === obj1); // true, lexical
this
by defaultobj2 = { method: func } console.log(obj2.method() === obj2); // true, dynamic this overrides lexical
this
Sorry for bringing up this issue again, I’m still a bit hazy as to what the arguments against such a solution are, especially after having seen the semantics of block lambdas.