Class double-bind

# Jason Orendorff (10 years ago)

Classes are specified to have an immutable inner binding even if they also declare a mutable lexical binding in the enclosing scope:

class Foo {
    foo() {
        // immutable inner binding 'Foo' is in scope here
    }
}
// mutable lexical binding 'Foo' is in scope here

Having two bindings can be tricky. Suppose I then do this:

Foo = wrapConstructorWithExtraLogging(Foo);

Now the two bindings have different values. If one of Foo's methods does new Foo, we don't get the extra logging.

Can we go back to having classes do what functions do? A single function never introduces two bindings; rather a named function expression has only the immutable inner binding and a function declaration only declares a variable.

# Mark S. Miller (10 years ago)

All else being equal, your suggestion seems like the obviously right thing, on at least consistency and least surprise grounds.

I know we discussed this. Anyone remember why we didn't do what Jason suggests? My memory of counter-arguments here is blank.

# Erik Arvidsson (10 years ago)

Classes are more similar to function expressions which do have an internal const binding.

One way to desugar ClassDeclaration is to desugar it into a let binding for a ClassExpression. [1]

let Foo = class Foo extends expr {};

I would prefer if we didn't change the spec, not because I think it is too late (it is) but because having an internal const binding is good because then you can rely on that it will never change.

[1] This is actually what we do in V8.

# Allen Wirfs-Brock (10 years ago)

see belolw

On Mar 2, 2015, at 1:35 PM, Erik Arvidsson wrote:

Classes are more similar to function expressions which do have an internal const binding.

One way to desugar ClassDeclaration is to desugar it into a let binding for a ClassExpression. [1]

let Foo = class Foo extends expr {};

I would prefer if we didn't change the spec, not because I think it is too late (it is) but because having an internal const binding is good because then you can rely on that it will never change.

[1] This is actually what we do in V8.

On Mon, Mar 2, 2015 at 10:12 PM Mark S. Miller <erights at google.com> wrote: All else being equal, your suggestion seems like the obviously right thing, on at least consistency and least surprise grounds.

I know we discussed this. Anyone remember why we didn't do what Jason suggests? My memory of counter-arguments here is blank.

On Mon, Mar 2, 2015 at 1:07 PM, Jason Orendorff <jason.orendorff at gmail.com> wrote: Classes are specified to have an immutable inner binding even if they also declare a mutable lexical binding in the enclosing scope:

class Foo {
    foo() {
        // immutable inner binding 'Foo' is in scope here
    }
}
// mutable lexical binding 'Foo' is in scope here

Having two bindings can be tricky. Suppose I then do this:

Foo = wrapConstructorWithExtraLogging(Foo);

Now the two bindings have different values. If one of Foo's methods does new Foo, we don't get the extra logging.

and conversely if we have something like this:

class Bar { constructor(n) {this.n=n} static get const() {return 42); op(x) { return new Bar(Bar.const )} };

the inner workings of the class would get very screwed up if somebody did: Bar = { };

the chain of logic I apply for arriving at the specified behavior is:

  1. There are many reasons a developer may want to refer to a class from within it's body and the obvious way of doing so should be reliable and tamper-proof.
  2. This behavior of function declarations is actually quite astonishing and if we had a do over on FunctionDeclaration we would probably give it a local name binding just like function expressions (we might fight about it a while, but I'm pretty sure that's where we would end up).
  3. Class declarations already differ from function declarations in a number of other ways, so class declaration consistency with function declaration on this one arguably buggy behavior is not a particularly strong position.
# Jason Orendorff (10 years ago)

On Mon, Mar 2, 2015 at 4:15 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

and conversely if we have something like this:

class Bar { constructor(n) {this.n=n} static get const() {return 42); op(x) { return new Bar(Bar.const )} };

the inner workings of the class would get very screwed up if somebody did: Bar = { };

...But that would just be a bug in the program. The same thing would happen if you overwrote any function or method anywhere with a random object. For new Bar to stop working at that point is what people will expect, and I don't think we do programmers any favors by mysteriously masking certain kinds of bug for a little while.

My example was something useful, something programmers will do on purpose, that goes silently wonky because of the double binding.

the chain of logic I apply for arriving at the specified behavior is:

  1. There are many reasons a developer may want to refer to a class from within it's body and the obvious way of doing so should be reliable and tamper-proof.

The reliability benefit is surely offset somewhat by how confusing this is.

If we wanted classes to be tamper-proof, then the outer binding would be immutable as well. It's not; it's mutable; yet every use case I can think of for actually assigning to it is silently broken due to the double binding.

I favor keeping it mutable, because that's useful and JS is the sort of language where you're allowed to hack some things. But as long as it's mutable there should not also be a second implicit binding seen by different code. One declaration creating two bindings + mutability = hall of mirrors.

  1. This behavior of function declarations is actually quite astonishing and if we had a do over on FunctionDeclaration we would probably give it a local name binding just like function expressions (we might fight about it a while, but I'm pretty sure that's where we would end up).

That's precisely what I thought, until I started thinking about what it'll actually be like to use this feature as a programmer.

To me, this issue explains why functions are the way they are.

  1. Class declarations already differ from function declarations in a number of other ways, so class declaration consistency with function declaration on this one arguably buggy behavior is not a particularly strong position.

Sure, if it were purely about matching what functions do for the sake of matchiness, I wouldn't be here.

# Allen Wirfs-Brock (10 years ago)

On Mar 2, 2015, at 3:14 PM, Jason Orendorff wrote:

On Mon, Mar 2, 2015 at 4:15 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

and conversely if we have something like this:

class Bar { constructor(n) {this.n=n} static get const() {return 42); op(x) { return new Bar(Bar.const )} };

the inner workings of the class would get very screwed up if somebody did: Bar = { };

...But that would just be a bug in the program. The same thing would happen if you overwrote any function or method anywhere with a random object. For new Bar to stop working at that point is what people will expect, and I don't think we do programmers any favors by mysteriously masking certain kinds of bug for a little while.

My example was something useful, something programmers will do on purpose, that goes silently wonky because of the double binding.

I didn't take the time to come up with a real example. But code within a class body that refers to the containing class is something I have seem many times. Some of the most common places you see this is in methods (instance or static) referring to "constants" defined as static properties of the class. Another place you see it is "static" methods referring to other static methods of the same class or applying 'new' to their class name. In most of these cases, there are actually better ways to do the same thing (referring to 'this' from within static methods, using 'this.constructor' within instance3 method) but many developer still use the direct name references.

Of course, using 'new this.construtor' internally would also break your logging wrapper. Which brings us back to what should probably be the major point.. Class are supposed to be abstractions that encapsulate their internal implementation details. Expecting that sort of wrapper to work an any sort of general situation is grossly violating that encapsulation.

The unusual/exceptional case is actually wanting to internally refer to that sort of mutable binding. But if you want to, you can:

let mutableName = class { foo() {new mutableName)}

it just should be the way things normally work.

the chain of logic I apply for arriving at the specified behavior is:

  1. There are many reasons a developer may want to refer to a class from within it's body and the obvious way of doing so should be reliable and tamper-proof.

The reliability benefit is surely offset somewhat by how confusing this is.

Confusing to who? Pretty much everyone I've seen discover the function binding equivalent (or have it explained to them for the first time) was shocked. What! 'function fact(n) {return n>1? n*fact(n-1):1}' doesn't necessarily recur on the same function? that's crazy! is the typical reaction.

Similarly, it would be crazy if: class Foo { static makeFoo() {return new Foo} }

didn't give you a factory method that created instances of Foo.

If we wanted classes to be tamper-proof, then the outer binding would be immutable as well. It's not; it's mutable; yet every use case I can think of for actually assigning to it is silently broken due to the double binding.

We needed global class bindings to be replaceable.

I favor keeping it mutable, because that's useful and JS is the sort of language where you're allowed to hack some things. But as long as it's mutable there should not also be a second implicit binding seen by different code. One declaration creating two bindings + mutability = hall of mirrors.

  1. This behavior of function declarations is actually quite astonishing and if we had a do over on FunctionDeclaration we would probably give it a local name binding just like function expressions (we might fight about it a while, but I'm pretty sure that's where we would end up).

That's precisely what I thought, until I started thinking about what it'll actually be like to use this feature as a programmer.

To me, this issue explains why functions are the way they are.

Not my understandings. Function declarations are the way they are because that's the way they were in May 1995. Function expressions got added in ES3 and added the local name binding for them. But didn't change function declarations (probably because it would have been a breaking change).

# Brendan Eich (10 years ago)

Allen Wirfs-Brock wrote:

That's precisely what I thought, until I started thinking about what

it'll actually be like to use this feature as a programmer.

To me, this issue explains why functions are the way they are.

Not my understandings. Function declarations are the way they are because that's the way they were inMay 1995. Function expressions got added in ES3 and added the local name binding for them. But didn't change function declarations (probably because it would have been a breaking change).

Among other reasons :-|.

Top-level functions binding one name, instead of two, avoids the problems Jason cites. It may make other problems, which require programmers to create a second binding that is hidden or otherwise protected from replacement, but that's a trade-off.

Do not pretend that the var f = function f(){} pattern advocated by some is more primitive than function f(){}. It isn't, and it wouldn't have been, even if I'd had time to do function expressions in 1995.

Rather, I wanted a lightweight, top-level let rec analogue, not only for mutual recursion, but also to enable top-down presentation. That's a win in JS when using function declarations.

ISTM that in ES6 as drafted, anyone wanting to do what Jason showed, a wanted thing:

 Foo = wrapConstructorWithExtraLogging(Foo);

will need to pre-arrange with Foo's author that internal uses of Foo which should refer to the replacement be denoted by outer.Foo or global.Foo.

This can be done, but it is potentially a big paper-cut, or pain-in-the-ass.

Going the other way on the trade-off, trying to protect against Foo = {}, is not saving a p.big p-phrase -- it's kicking the can down the road on what looks like a bug to be smoked out.

At this point we may have to take the hit, but I'm making you read this to talk back to the imprecations against function declarations and their single bindings. Double bindings are "now you have two problems" when one binding is mutable -- no upside per se, however much you want the immutable inner binding (and sometimes you do not want).

# Claude Pache (10 years ago)

A simple solution is to make:

class Foo { /* ... */ }

equivalent to:

const Foo = class Foo { /* ... */ }

instead of the equivalent desugaring with let. That way, anyone who want to play with a same-named external binding must be explicit about their intention:

let Foo = class { /* ... */ } // one binding
let Foo = class Foo { /* .... */ } // two bindings
# Brendan Eich (10 years ago)

Claude Pache wrote:

A simple solution is to make:

 class Foo { /* ... */ }

equivalent to:

 const Foo = class Foo { /* ... */ }

instead of the equivalent desugaring with let.

The ES6 design has "classes as sugar", which is an imprecise slogan, but useful. For some reason we settled on a mutable outer binding in block scope: let not const. That "some reason" may have been the expectation that classes desugar to prototypal function-as-constructor-with-prototype-properties lowerings, but still: no reason given ES6 const and let to prefer let.

I suspect it'll be hard to change anything for ES6 at this stage, especially to make class bind a const instead of a let binding -- but your point still stands:

That way, anyone who want to play with a same-named external binding must be explicit about their intention:

 let Foo = class { /* ... */ } // one binding
 let Foo = class Foo { /* .... */ } // two bindings

Anyone who wants to support replacement-for-wrapping will have to use the first form.

Let's see if this becomes the norm, on that basis. I hope it doesn't, because if so, we picked the wrong (two-binding-not-one) desugaring.

# Jason Orendorff (10 years ago)

On Mon, Mar 2, 2015 at 5:54 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

I didn't take the time to come up with a real example. But code within a class body that refers to the containing class is something I have seem many times. Some of the most common places you see this is in methods (instance or static) referring to "constants" defined as static properties of the class. Another place you see it is "static" methods referring to other static methods of the same class or applying 'new' to their class name. In most of these cases, there are actually better ways to do the same thing (referring to 'this' from within static methods, using 'this.constructor' within instance3 method) but many developer still use the direct name references.

Yes, we agree on all this. What's a bug is Bar = { };.

Of course, using 'new this.construtor' internally would also break your logging wrapper.

The wrapper could be either a subclass (which will work inasmuch as a subclass can substitute for the base class) or a proxy (which will work inasmuch as a proxy can substitute for its target object). Or it could just mutate the constructor link.

Which brings us back to what should probably be the major point.. Class are supposed to be abstractions that encapsulate their internal implementation details. Expecting that sort of wrapper to work an any sort of general situation is grossly violating that encapsulation.

Hmm. Usually the stronger an abstraction, the better wrapping works. It's funny that it turns out not to work that way in this case.

To me, what's happening here is that the double-binding design is just... weird. So unsurprisingly, it has weird consequences for users. I don't think it even matters where one stands on the classes-as-sugar/classes-as-abstraction spectrum.

# Jason Orendorff (10 years ago)

Oops, accidentally sent this only to Allen.

---------- Forwarded message ---------- From: Jason Orendorff <jason.orendorff at gmail.com>

Date: Tue, Mar 3, 2015 at 11:34 AM Subject: Re: Class double-bind To: Allen Wirfs-Brock <allen at wirfs-brock.com>

On Mon, Mar 2, 2015 at 5:54 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Pretty much everyone I've seen discover the function binding equivalent (or have it explained to them for the first time) was shocked. What! 'function fact(n) {return n>1? n*fact(n-1):1}' doesn't necessarily recur on the same function? that's crazy! is the typical reaction.

Perhaps this is a little overstated? In Scheme, Python, Lua, Ruby, and Smalltalk, code in a function/class/method that refer to it by name refer to the external binding. That binding is mutable in all those languages except Ruby and Smalltalk (and even in those, I think you can mutate it, but the language discourages it).

So basically every language in this space has the same behavior. JS differs only in that it has a named-function-expression syntax, which I think the others lack.

I can't think of a language where two bindings are created and they can then diverge. This is novel weirdness.

Similarly, it would be crazy if: class Foo { static makeFoo() {return new Foo} }

didn't give you a factory method that created instances of Foo.

Well, you're calling every existing dynamic language with classes crazy. I don't think that's very useful.

If we wanted classes to be tamper-proof, then the outer binding would be immutable as well. It's not; it's mutable; yet every use case I can think of for actually assigning to it is silently broken due to the double binding.

We needed global class bindings to be replaceable.

That makes sense.

# Jason Orendorff (10 years ago)

I guess we are just going to disagree here. Double bindings with mutability still seem clearly bad. I can't make sense of the rationale that classes can cope with every external binding being mutable except for, somehow, their own name, and we're doing users a favor by "protecting" them from "tampering" for just this one binding in this one place.

# Andreas Rossberg (10 years ago)

For the record, I strongly dislike the function behaviour. Turning a function expression into a declaration silently changes the meaning of internal recursive references, in ways that many people find very surprising. That is an unnecessary pitfall.

Your argument essentially is that you want to be able to do some AOP-style hacking on (otherwise internal) class bindings. But as Allen pointed out, that isn't reliable anyway, nor would it work for classes that do not happen to be defined using class declaration syntax. So I think it is a fairly weak argument. It is even preferable that the language does not encourage such brittle patterns.

In summary, I'm glad that we did not adopt this behaviour for classes.

(On the other hand, I do think that it probably was a serious mistakes to make class bindings mutable.)

# Mark S. Miller (10 years ago)

On Wed, Mar 4, 2015 at 6:41 AM, Andreas Rossberg <rossberg at google.com>

wrote: [...]

(On the other hand, I do think that it probably was a serious mistakes to make class bindings mutable.)

Will be part of the "const class" proposal from < harmony:classes#const> to be revived

at some point for a future ES. Such const classes also have much in common with classes in your <positive-adjective>Script proposal, and probably

should have more. Indeed, I think we should unbundle much of the classes from <positive-adjective>Script into const classes.

# Allen Wirfs-Brock (10 years ago)

On Mar 4, 2015, at 4:21 AM, Jason Orendorff wrote:

Oops, accidentally sent this only to Allen.

and I replied as follows:

On Mar 3, 2015, at 9:34 AM, Jason Orendorff wrote:

On Mon, Mar 2, 2015 at 5:54 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Pretty much everyone I've seen discover the function binding equivalent (or have it explained to them for the first time) was shocked. What! 'function fact(n) {return n>1? n*fact(n-1):1}' doesn't necessarily recur on the same function? that's crazy! is the typical reaction.

Perhaps this is a little overstated? In Scheme, Python, Lua, Ruby, and Smalltalk, code in a function/class/method that refer to it by name refer to the external binding. That binding is mutable in all those languages except Ruby and Smalltalk (and even in those, I think you can mutate it, but the language discourages it).

indeed: Smalltalk at:#className put: whatever.

but it would typically be frowned upon.

So basically every language in this space has the same behavior. JS differs only in that it has a named-function-expression syntax, which I think the others lack.

I agree that strictly from a binding perspective it's not particularly crazy, but it is surprising as most programmers don't think about function and class declarationa primarily as binding forms. The focus is on defining the entity that is the initial value (and typically only value) of the binding rather than on the binding itself.

The real anomaly is that the exact same function definition text (eg: function x() {x()} ) in some context's creates a local binding for 'x' and in others does not.

We can avoid that anomaly for class definitions by always providing a local binding if an identifier follows the 'class'. If we do that, then a class declaration like: class x {}; can be understood and even implemented as desugaring into: let x = class x{};

That's essentially what the ES6 spec. describes.

This gives a single internal naming semantics for all class definition which is how most people think about it most of the time. In the rarer situations where developer really want to manipulate the bindings, it is good that they make that intention clear by using: let x = class {};

I can't think of a language where two bindings are created and they can then diverge. This is novel weirdness.

In C++/Java/C# etc. you don't see it because the corresponding declarations create immutable bindings. I agree that it would have been nice of we could have done that. From that perspective having an immutable class name binding that is visible from the class body isn't novel at all. The two bindings is simply an specification level hack to provide that without using an immutable binding.

Similarly, it would be crazy if: class Foo { static makeFoo() {return new Foo} }

didn't give you a factory method that created instances of Foo.

Well, you're calling every existing dynamic language with classes crazy. I don't think that's very useful.

Oh, it would be interesting to talk about how central (or not) the role of binding mechanisms is in various languages, but it probably isn't going to help us reach agreement right now.

I will add that I don't find your class wrapping use case very compelling. I think it is much more applicable for simple functions than for classes. Creating a high fidelity class wrapper is challenging when you factor in things like the 'new' operator, 'instanceof', and downstream subclassing of the now wrapped function. I don't think we're going to see nearly as often as we see self referential class bodies.

# Brendan Eich (10 years ago)

Andreas Rossberg wrote:

For the record, I strongly dislike the function behaviour. Turning a function expression into a declaration silently changes the meaning of internal recursive references, in ways that many people find very surprising. That is an unnecessary pitfall.

Also for the record, I wish I'd done proper let rec, or let/and/in a la ML :-).

But I didn't and new things to add to JS cannot start from a blank slate. Given this, I agree with your

(On the other hand, I do think that it probably was a serious

mistakes to make class bindings mutable.)

Replying to Allen on this point in a minute.

# Brendan Eich (10 years ago)

Allen Wirfs-Brock wrote:

This is novel weirdness.

In C++/Java/C# etc. you don't see it because the corresponding declarations create immutable bindings. I agree that it would have been nice of we could have done that.

Why could we not have?

I asked this up-thread. What was the rationale for let not const binding via class declarations? I honestly do not remember us considering const. Did we just "default" into let because of the historical (var) default binding form being mutable? If so, is it really too late?

Cc'ing Arv in case he can check via Traceur telemetry whether anyone counts on let-not-const from class.

# Andreas Rossberg (10 years ago)

On 5 March 2015 at 04:57, Brendan Eich <brendan at mozilla.org> wrote:

Allen Wirfs-Brock wrote:

This is novel weirdness.

In C++/Java/C# etc. you don't see it because the corresponding declarations create immutable bindings. I agree that it would have been nice of we could have done that.

Why could we not have?

I asked this up-thread. What was the rationale for let not const binding via class declarations? I honestly do not remember us considering const. Did we just "default" into let because of the historical (var) default binding form being mutable? If so, is it really too late?

I seem to remember a (brief) discussion about this, where the main argument for mutable was that it was "natural" for JS. Allen probably remembers more of the details.

It would be totally awesome if we could still correct this.

# Erik Arvidsson (10 years ago)

Traceur does not give any history on this but I also remember having this discussion in a f2f meeting. It was all about "js has always been mutable, lets not change that. If you want immutability you have it with const f = class {}".

# Brendan Eich (10 years ago)

Erik Arvidsson wrote:

Traceur does not give any history on this but I also remember having this discussion in a f2f meeting. It was all about "js has always been mutable, lets not change that. If you want immutability you have it with const f = class {}".

Yeah, that fits the groove laid down by var -- but we didn't advert to the double-binding issue then. Sounds like Andreas (and Mark?) would've wanted const not let, in that alternate history. WDYT?

# Allen Wirfs-Brock (10 years ago)

On Mar 5, 2015, at 2:52 AM, Erik Arvidsson wrote:

Traceur does not give any history on this but I also remember having this discussion in a f2f meeting. It was all about "js has always been mutable, lets not change that. If you want immutability you have it with const f = class {}".

I think that's pretty much it. Plus I think there was particular concern about the script global scope and the expectation that globals defined by one script should be patchable by another.

But perhaps our insight in these matters have evolved since then.

Editorially it would be a trivial change to make class declaration produce immutable bindings. But if we were going to decide that I'd prefer that we achieved TC39 consensus on the matter within the next week , so the changes could be made before the March meeting. Does anybody want to try to champion this through that process?

# Boris Zbarsky (10 years ago)

On 3/5/15 11:13 AM, Allen Wirfs-Brock wrote:

Editorially it would be a trivial change to make class declaration produce immutable bindings.

Just to be clear, this would make class declarations behave differently from builtins, both the ES kind and the IDL kind, right?

I'm not saying that's a problem necessarily, but worth keeping in mind.

# Kevin Smith (10 years ago)

Just to be clear, this would make class declarations behave differently from builtins, both the ES kind and the IDL kind, right?

I'm not saying that's a problem necessarily, but worth keeping in mind.

Good point. I think that we need to be looking ahead toward const class, and leave ES6 class declaration bindings as is.

# Domenic Denicola (10 years ago)

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Boris Zbarsky

Just to be clear, this would make class declarations behave differently from builtins, both the ES kind and the IDL kind, right?

I think that would be true if your mental model of built-ins is that, in the global scope, someone did class Array { ... } or class Window { ... }.

But my mental model has been that, in some other scope (probably a closure to encapsulate private helper functions, or a module) someone does global.Array = class Array { ... } and global.Window = class Window { ... }. That is, at least, what we do in jsdom.

# Allen Wirfs-Brock (10 years ago)

On Mar 5, 2015, at 8:50 AM, Domenic Denicola wrote:

From: es-discuss [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Boris Zbarsky

Just to be clear, this would make class declarations behave differently from builtins, both the ES kind and the IDL kind, right?

I think that would be true if your mental model of built-ins is that, in the global scope, someone did class Array { ... } or class Window { ... }.

But my mental model has been that, in some other scope (probably a closure to encapsulate private helper functions, or a module) someone does global.Array = class Array { ... } and global.Window = class Window { ... }. That is, at least, what we do in jsdom.

In addition, global class declaration don't create global object properties. So the global built-in can't be considered to be as if defined by global class declarations.

# Mark Miller (10 years ago)

If it were just a question of non-const-classes being too mutable, well, they're everywhere already ridiculously too mutable in line with most things in JS. It would be coherent to wait for const classes to repair the mutability of the binding at the same time it repairs the mutability of the prototype chain, etc.

However, the double-binding issue makes this weirder. If non-const-class declarations were like non-const-function declarations, where there is only one binding per defining occurrence, then I would fully agree. But this issue of one defining occurrence creating two bindings that can diverge is a new level of unpleasantness. I agree this calls for the issue to be fixed now in ES6 if we can, for non-const-classes.

# Kevin Smith (10 years ago)

However, the double-binding issue makes this weirder. If non-const-class declarations were like non-const-function declarations, where there is only one binding per defining occurrence, then I would fully agree. But this issue of one defining occurrence creating two bindings that can diverge is a new level of unpleasantness. I agree this calls for the issue to be fixed now in ES6 if we can, for non-const-classes.

OK - I see. So on one side (double-binding) the risk is that someone will change the outer binding and violate an implicit assumption that references inside the class body and outside point to the same thing. On the other side, the risk is that we disable hackery like:

class C {}
if (someCondition) {
 C = class OtherC {};
}

But you can always write the above as:

let C = class {};
if (someCondition) {
 C = class OtherC {};
}

I think you've sold me. I'm worried that there might be other hacker-use-cases that I'm not considering, though.

# Luke Scott (10 years ago)

On Mar 5, 2015, at 9:20 AM, Kevin Smith <zenparsing at gmail.com> wrote:

However, the double-binding issue makes this weirder. If non-const-class declarations were like non-const-function declarations, where there is only one binding per defining occurrence, then I would fully agree. But this issue of one defining occurrence creating two bindings that can diverge is a new level of unpleasantness. I agree this calls for the issue to be fixed now in ES6 if we can, for non-const-classes.

OK - I see. So on one side (double-binding) the risk is that someone will change the outer binding and violate an implicit assumption that references inside the class body and outside point to the same thing. On the other side, the risk is that we disable hackery like:

class C {}
if (someCondition) {
 C = class OtherC {};
}

But you can always write the above as:

let C = class {};
if (someCondition) {
 C = class OtherC {};
}

I think you've sold me. I'm worried that there might be other hacker-use-cases that I'm not considering, though.

Isn’t making Foo a const inconsistent with the rest of the language? You can redefine a function. Currently you can do this anyway:

const C = class {}; if (someCondition) { C = class OtherC {}; }

If not fixing the double bind not an option? If it isn’t an option, why not make a class reference itself by name be an undefined error:

class Foo {
  static makeFoo() {
    return new Foo(); // Foo should be undefined!
  }
}
// Foo should be considered defined at this point.
``

Because you can do this and it would likely not be correct either:

class Foo { static makeFoo() { return new Foo(); } foo() {

} }

var FooWithLogging = class extends Foo { foo() { super.foo(); console.log(“log!”); } }

FooWithLogging.makeFoo().foo();


In most (all?) of these situations `this.constructor` or `this.prototype.constructor` should be used anyway, right? Doing that in Jason’s example bypasses the binding issue.

Although getting at `constructor` is a bit cumbersome, so it would be nice if a `self` keyword existed:

class Foo { method() { new self(); // same as new this.constructor() } static method() { new self(); // same as new this.prototype.constructor() } }

function foo() { self(); // same thing as foo() }


Is there any reason for a class to reference itself by name where getting its `constructor` thru `this` wouldn’t work?

--
Luke
# Rick Waldron (10 years ago)

On Thu, Mar 5, 2015 at 1:40 PM Luke Scott <luke at cywh.com> wrote:

On Mar 5, 2015, at 9:20 AM, Kevin Smith <zenparsing at gmail.com> wrote:

However, the double-binding issue makes this weirder. If non-const-class

declarations were like non-const-function declarations, where there is only one binding per defining occurrence, then I would fully agree. But this issue of one defining occurrence creating two bindings that can diverge is a new level of unpleasantness. I agree this calls for the issue to be fixed now in ES6 if we can, for non-const-classes.

OK - I see. So on one side (double-binding) the risk is that someone will change the outer binding and violate an implicit assumption that references inside the class body and outside point to the same thing. On the other side, the risk is that we disable hackery like:

class C {}
if (someCondition) {
 C = class OtherC {};
}

But you can always write the above as:

let C = class {};
if (someCondition) {
 C = class OtherC {};
}

I think you've sold me. I'm worried that there might be other hacker-use-cases that I'm not considering, though.

Isn’t making Foo a const inconsistent with the rest of the language? You can redefine a function. Currently you can do this anyway:

const C = class {}; if (someCondition) { C = class OtherC {}; }

If not fixing the double bind not an option? If it isn’t an option, why not make a class reference itself by name be an undefined error:

class Foo {
  static makeFoo() {
    return new Foo(); // Foo should be undefined!
  }
}
// Foo should be considered defined at this point.
``

By the time Foo.makeFoo() becomes callable, Foo will be defined, so this doesn't make sense.

Because you can do this and it would likely not be correct either:

class Foo {
  static makeFoo() {
    return new Foo();
  }
  foo() {

  }
}

var FooWithLogging = class extends Foo {
  foo() {
    super.foo();
    console.log(“log!”);
  }
}

FooWithLogging.makeFoo().foo();

In most (all?) of these situations this.constructor or this.prototype.constructor should be used anyway, right? Doing that in Jason’s example bypasses the binding issue.

Although getting at constructor is a bit cumbersome, so it would be nice if a self keyword existed:

Maybe?

class Foo {
  method() {
    new self(); // same as `new this.constructor()`
  }
  static method() {
    new self(); // same as `new this.prototype.constructor()`
  }
}

That isn't impossible, but...

function foo() { self(); // same thing as foo() }


This would be a breaking change: self is not a reserved or future reserved word (in any mode).

# Brendan Eich (10 years ago)

Luke Scott wrote:

Currently you can do this anyway:

const C = class {}; if (someCondition) { C = class OtherC {}; }

You can't: const is assign-once in ES6, and only from the initialiser in the declaration.