Non-method functions and this

# Axel Rauschmayer (14 years ago)

Sorry for bringing this up again, but I don’t think this particular question has been answered yet:

(1) I would prefer non-method functions not to have a binding for |this| at all.

(2) The solution for ES.next seems to be to (e.g. internally, via =>) bind to |this| of the surrounding method.

Isn’t (1) a better solution? I’m assuming (2) is done, because (1) would be too radical a change (execution contexts etc.)? (1) could be simulated by always binding |this| in non-method functions, but that isn’t done because of performance issues(?)

Thanks!

Axel

# Brendan Eich (14 years ago)

On May 15, 2011, at 1:09 PM, Axel Rauschmayer wrote:

Sorry for bringing this up again, but I don’t think this particular question has been answered yet:

(1) I would prefer non-method functions not to have a binding for |this| at all.

How do you define "non-method"?

It would make an early error for many functions written today as function declarations, which use |this| because they are assigned as property values (i.e. as methods), to call the |this| usage in such functions an early error. That would be a harsh migration tax, even though compile-time. We are not changing Harmony that much.

On the other hand, if you mean a runtime error, that's right out as an untestable migration booby trap.

(2) The solution for ES.next seems to be to (e.g. internally, via =>) bind to |this| of the surrounding method.

The strawman:arrow_function_syntax proposal is still a strawman. Whatever we do, lexical |this| is wanted, for the

function outer () { var self = this; return function () {... self ...}; }

cliché. This is too common a pattern to ignore.

Isn’t (1) a better solution?

Why?

Again, what's a "non-method function"?

I’m assuming (2) is done, because (1) would be too radical a change (execution contexts etc.)?

Think migration tax. Many functions whether declared or expressed use |this| today. They are used as methods but the linkage to their method name (property of an object) is not declared or even expressed nearby. How would you forbid such functions to use |this|, or allow them to but forbid others that are not called as methods to use |this|/

(1) could be simulated by always binding |this| in non-method functions, but that isn’t done because of performance issues(?)

There is no "non-method function" in JS today. You are thinking of call expressions, how |this| is bound dynamically. Programmer intent is not expressible directly in the language, and whatever extensions we add (e.g. for methods in object intiialisers, proposed in several strawmen), we aren't going to break the existing code that declares and expresses functions that use |this|.

# Axel Rauschmayer (14 years ago)

Caveat: I am aware that what I’m suggesting might clash too hardly with current semantics (how to handle invocation, apply(), call(), etc.), but I am interested in the arguments against it.

(1) I would prefer non-method functions not to have a binding for |this| at all.

How do you define "non-method"?

A function that is not invoked as method. Right now, the same kind of construct is used for both true functions and methods. I’m proposing a new construct (similar to the distinction that Python makes): a function that does not have an implicit |this| parameter.

It would make an early error for many functions written today as function declarations, which use |this| because they are assigned as property values (i.e. as methods), to call the |this| usage in such functions an early error. That would be a harsh migration tax, even though compile-time. We are not changing Harmony that much.

Right. It could be introduced via the arrow function syntax, with the "function" operator remaining as is:

var obj = { id: "123", method: (x) => { // same as function operator [1,2].forEach((i) -> { // new construct console.log(this.id + ": " + i + " - " + x); }); } }

Thus: => and function: |this| is an implicit argument (“methods”) ->: |this| is not an implicit argument; currently simulated via binding (“functions”)

The arrows are swapped compared to the strawman, because => is more “heavy-weight” here.

Isn’t (1) a better solution?

Why?

It is easier to understand for beginners. |this| always being an implicit parameter trips up many newbies. I also find it a cleaner way of getting lexical |this|.

I’m assuming (2) is done, because (1) would be too radical a change (execution contexts etc.)?

Think migration tax. Many functions whether declared or expressed use |this| today. They are used as methods but the linkage to their method name (property of an object) is not declared or even expressed nearby. How would you forbid such functions to use |this|, or allow them to but forbid others that are not called as methods to use |this|/

See above.

Greetings,

Axel

# Brendan Eich (14 years ago)

On May 15, 2011, at 2:36 PM, Axel Rauschmayer wrote:

Caveat: I am aware that what I’m suggesting might clash too hardly with current semantics (how to handle invocation, apply(), call(), etc.), but I am interested in the arguments against it.

The strawman contains bits of rationale, please look for them.

(1) I would prefer non-method functions not to have a binding for |this| at all.

How do you define "non-method"?

A function that is not invoked as method. Right now, the same kind of construct is used for both true functions and methods. I’m proposing a new construct (similar to the distinction that Python makes): a function that does not have an implicit |this| parameter.

It would make an early error for many functions written today as function declarations, which use |this| because they are assigned as property values (i.e. as methods), to call the |this| usage in such functions an early error. That would be a harsh migration tax, even though compile-time. We are not changing Harmony that much.

Right. It could be introduced via the arrow function syntax, with the "function" operator remaining as is:

var obj = { id: "123", method: (x) => { // same as function operator [1,2].forEach((i) -> { // new construct console.log(this.id + ": " + i + " - " + x); }); } }

The goal of strawman:arrow_function_syntax and the earlier, somewhat abandoned (outside of Traceur) strawman:shorter_function_syntax, is to make "just syntax" at the minimum. Not new kinds of functions, one that has no |this| parameter.

Thus: => and function: |this| is an implicit argument (“methods”) ->: |this| is not an implicit argument; currently simulated via binding (“functions”)

The arrows are swapped compared to the strawman, because => is more “heavy-weight” here.

How is implicit (let's please say "dynamic") |this| more heavy-weight? It's what is in JS today, optimized heavily in current VMs, not requiring a cloned function object to bind a particular |this| to the function value.

In contrast, binding |this| has noticeable cost even for JS hackers, if overused. It is the exception, by static source function call vs. bind counting anyway.

The CoffeeScript decision to use fat-arrow to connote the higher cost of bind (or the closure-based equivalent used by the CoffeeScript compiler) puts the fat-arrow on the heavy-weight target, ITSM.

Isn’t (1) a better solution?

Why?

It is easier to understand for beginners. |this| always being an implicit parameter trips up many newbies. I also find it a cleaner way of getting lexical |this|.

As you seemed to grant, we are not changing anything to do with |this| for function declarations and expressions spelled using 'function'. Making arrow functions differ subtly is a learning burden too. (So is deviating from CoffeeScript, FWIW.)

# Axel Rauschmayer (14 years ago)

Oh, I see. But there's still a problem. Without a class or similar declarative form to associate the method with an object or class of objects, making the function syntax reject or accept |this| in the function body is not enough. A method needs to be enclosed by a class.

We have proposals to do that, and for such methods, |this| binding should be unsurprising and trouble-free.

Interesting. I thought that functions were methods (=dynamic |this|) by default and that function definitions nested inside them were the problem (=extra work via bind() to get lexical this).

Nested functions not called as method are a use-case for lexical-|this|, met poorly today via .bind(this) or equivalent. Yes, that's true -- does that lead back to which arrow should be "fat" vs. "thin"? I don't see how it otherwise bears on things.

We want one arrow-function form to inherit lexical |this|, the other to have dynamic-|this|. The full strawman even allows "soft binding" of |this| via explicit (this ??= default_this) parameterization. But in any event, the only question is what syntax to choose for the several kinds of |this| binding. Right?

Right. The heavy arrow correctly visualizes the “extra work” (of binding/lexical |this|). I thought the proposal that you talked about did something more “clever” (e.g. change the semantics of functions that are sure to be methods). But given all of the constraints (migration tax, heavy-weight lexical |this|, existing function semantics, etc.), I don’t see how one could do better than the strawman. I especially like how arrow function literals look in object literals.

# David Herman (14 years ago)

How do you define "non-method"?

A function that is not invoked as method. Right now, the same kind of construct is used for both true functions and methods. I’m proposing a new construct (similar to the distinction that Python makes): a function that does not have an implicit |this| parameter.

Hm, you've lost me... Your proposal seems to have several possible interpretations:

a) A compile-time error at the definition site for a function that will ever be invoked as a function. This requires the ability to predict the future, which turns out to be kinda hard. ;-P

b) A compile-time error at the definition site for a function that is not defined inside of an object. This would be way too restrictive; programmers rightfully use the ability to define a function and then put it into objects afterwards.

function m(x, y, z) { ... this ... }
obj1.m = obj2.m = m;

or even

obj.m = function(x, y, z) { ... this ... }

c) At runtime, a function invoked as a non-method gets a |this|-binding of undefined. This is already the semantics of ES5 strict and therefore Harmony.

d) At runtime, a function invoked as a non-method gets a dynamic ReferenceError if it tries to refer to |this|. This would just be kind of obnoxious, I think, since if the function wants to test whether it's been called as a non-method it has to do something like

let nonMethod = false;
try { eval("this") } catch (e) { nonMethod = true }

That seems unfortunate.

# Sam Tobin-Hochstadt (14 years ago)

On Mon, May 16, 2011 at 10:15 AM, David Herman <dherman at mozilla.com> wrote:

d) At runtime, a function invoked as a non-method gets a dynamic ReferenceError if it tries to refer to |this|. This would just be kind of obnoxious, I think, since if the function wants to test whether it's been called as a non-method it has to do something like

let nonMethod = false;    try { eval("this") } catch (e) { nonMethod = true }

There's also more-obnoxious (e), where any function that includes a reference to |this| produces a ReferenceError immediately when invoked, instead of when (or if) it refers to |this|. That's what I thought Axel was suggesting.

# Axel Rauschmayer (14 years ago)

Yeah, sorry, I was wrong about the need for two constructs.

Example code:

var obj = { id: "ac3fb", method: function(self, msgs) { msgs.forEach(function(msg) { console.log(self.id+": "+msg); }); } };

// Invoke as method obj.method([ "hello", "world" ]);

// Invoke as function var func = obj.method; func(obj, [ "hello", "world" ]);

Notes:

  • "self" is just another parameter, it can be called anything.
  • Function.prototype.call() and Function.prototype.apply() would have one parameter less.
  • IIRC, this is more or less how Python works.
  • Probably not worth it, migration-cost-wise.

As an aside, I'm surprised that (ES5) JavaScript interprets the following as a method call: (obj.method)(arg)

It seems like the temporary result of the expression obj.method includes a value for "this" (which is lost after making the assignment func = obj.method).

# David Herman (14 years ago)
  • "self" is just another parameter, it can be called anything.
  • Function.prototype.call() and Function.prototype.apply() would have one parameter less.
  • IIRC, this is more or less how Python works.
  • Probably not worth it, migration-cost-wise.

This breaks the web, so regardless of whether it's worth it, it's just not possible. Harmony and non-Harmony share a heap with the same built-in globals. If you change Function.prototype.call and Function.prototype.apply, you break the web.

As an aside, I'm surprised that (ES5) JavaScript interprets the following as a method call: (obj.method)(arg)

It seems like the temporary result of the expression obj.method includes a value for "this" (which is lost after making the assignment func = obj.method).

That's not how the language is specified; there's no temporary variable introduced.

Briefly: ES has been specified since time immemorial with a fictional kind of value known as a "Reference." You can think of this as a first-class lvalue: it's a value that encapsulates a reference to an assignable location (in the heap, or possibly the stack, depending on the implementation). Many operations in the language automatically dereference a reference value, i.e. retrieve the value it points to, so most of the time these reference values aren't something a JS program can get its hand on directly. But paren expressions don't automatically dereference their result, they just pass reference values on through to their containing expression.

So in your example, the expression |obj.method| produces an "object reference" (roughly, a pointer to the |method| property of the |obj| object), and the paren expression just passes that object reference through to the call. The method call then uses the base object of the object reference, which is |obj|, as the |this|-binding.

# Kyle Simpson (14 years ago)

d) At runtime, a function invoked as a non-method gets a dynamic ReferenceError if it tries to refer to |this|. This would just be kind of obnoxious, I think, since if the function wants to test whether it's been called as a non-method it has to do something like

let nonMethod = false; try { eval("this") } catch (e) { nonMethod = true }

That seems unfortunate.

Couldn't the error only be issued if this was used either in an assignment fashion (either as an "lvalue" or "rvalue", or de-referenced with the . or [] operators (which should even be statically determinable, right?)?

That way, typeof this, this == undefined, etc would be safe, as in your example above, but this.foo, var self = this, etc would throw a ReferenceError error (most would anyway, if this was truly undefined).

OTOH, it seems like this is more a place for JSLint type assertions rather than something to be enforced by the language engine (either at compile-time or run-time).

  • Function.prototype.call() and Function.prototype.apply() would have one parameter less.

Yeah, I agree with others that while this might be nice, it would break the web (either with compile-time or run-time checking). I always just pass null for the first param, as I think most people do when it's known that this isn't going to be used.

It's definitely annoying that in ES3 and ES5 non-strict, such usage will still result in this defaulting to window instead of truly undefined.

On a side note, this is also a case where it would be nice if a comma-list of parameters to a function call (or array-initialization) could have "empty" locations that default to undefined, like Function.prototype.apply( ,...), but I doubt that'd ever fly. :)

# Axel Rauschmayer (14 years ago)

This breaks the web, so regardless of whether it's worth it, it's just not possible. Harmony and non-Harmony share a heap with the same built-in globals. If you change Function.prototype.call and Function.prototype.apply, you break the web.

I suspect that a new type for the new-style functions would have to be introduced anyway => not practical.

As an aside, I'm surprised that (ES5) JavaScript interprets the following as a method call: (obj.method)(arg)

It seems like the temporary result of the expression obj.method includes a value for "this" (which is lost after making the assignment func = obj.method).

That's not how the language is specified; there's no temporary variable introduced.

Briefly: ES has been specified since time immemorial with a fictional kind of value known as a "Reference." You can think of this as a first-class lvalue: it's a value that encapsulates a reference to an assignable location (in the heap, or possibly the stack, depending on the implementation). Many operations in the language automatically dereference a reference value, i.e. retrieve the value it points to, so most of the time these reference values aren't something a JS program can get its hand on directly. But paren expressions don't automatically dereference their result, they just pass reference values on through to their containing expression.

So in your example, the expression |obj.method| produces an "object reference" (roughly, a pointer to the |method| property of the |obj| object), and the paren expression just passes that object reference through to the call. The method call then uses the base object of the object reference, which is |obj|, as the |this|-binding.

Thanks for explaining the details. With "temporary result", I meant what you call "object reference". Similar to Common Lisp places (setf etc.), I suppose. If you use the conditional operator, you can force the dereferencing of an object reference:

(true ? obj.method : null)(arg)

# Brendan Eich (14 years ago)

On May 16, 2011, at 1:36 PM, Axel Rauschmayer wrote:

Thanks for explaining the details. With "temporary result", I meant what you call "object reference". Similar to Common Lisp places (setf etc.), I suppose. If you use the conditional operator, you can force the dereferencing of an object reference:

(true ? obj.method : null)(arg)

Right, or the comma operator, or && or ||.