Class double-bind

Allen Wirfs-Brock allen at wirfs-brock.com
Mon Mar 2 23:54:16 UTC 2015


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

> 
>> 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.
> 
> Sure, if it were purely about matching what functions do for the sake
> of matchiness, I wouldn't be here.
> 
> -j
> 



More information about the es-discuss mailing list