I noted some open issues on "Classes with Trait Composition"

classic Classic list List threaded Threaded
78 messages Options
1234
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Mark S. Miller-2
Hi Andreas, yes we have a long history of consider this shape, in fact much longer than the current shape. The final state of proposals along these lines is <http://wiki.ecmascript.org/doku.php?id=strawman:classes_with_trait_composition&rev=1299750065>.

This approach actually lead to a much more elegant way of providing encapsulation: by the objects-as-closures pattern, where the methods are own methods of the instance lexically capturing the constructor's lexical context. However, we finally gave up on this for two closely related reasons:

1) Starting from scratch, there's no problem engineering a VM to make objects-as-closures efficient, especially given the semi-static analysis that class proposal was designed to enable. However, JS VM implementors are not starting from scratch. Fitting such a new optimization into existing heavily optimized engines was thought to be a hard sell. Especially since all major VM implementors would need to agree.

2) The conventional JS pattern is to place methods on the prototype, not the instance, and many felt that the main thing classes need to provide is a syntax to make this traditional semantics easier to express.

Another variation of your suggestion that Tom suggested is that you mix instance initialization and class/prototype initialization together in the class body. This obscures both time-of-execution and scope. Methods on the prototype do cannot have the constructor parameters in scope.


On Thu, May 19, 2011 at 5:36 AM, Andreas Rossberg <[hidden email]> wrote:
My apologies if this has been discussed to death before -- well,
actually, I'd be surprised if it hasn't (pointers would be welcome).

I think it is worth noting that the baroque notation for defining
constructors that we see in the C++ / Java / C# world primarily is an
artefact of the desire to allow multiple constructors with overloading
in those languages. We don't have that issue in JS, so I wonder why we
cannot go for something more elegant? There is precedent in other
OOPLs (off the top of my head, e.g. Scala and OCaml) for putting the
constructor arguments on the class head directly, and executing the
class body like a block when the constructor is invoked. AFAICS:

-- This approach is significantly slimmer (and, I'd argue, more
readable) than the discussed alternatives, without needing any
keywords:

class Point(x0, y0) {
 public x = x0
 public y = y0
}

-- It naturally allows what Bob was suggesting:

class Point {  // no argument list would be shorthand for (), just
like when invoking new
 public x = 0
 public y = 0
}

-- It avoids additional hoops with initializing const attributes:

class ImmutablePoint(x0, y0) {
 const x = x0  // just like elsewhere
 const y = y0
}

-- The constructor arguments naturally are in the scope of the entire
object, so often you do not even need to introduce explicit (private)
fields to store them:

class Point(x, y) {
 public function abs() { return Math.sqrt(x*x, y*y) }
}

/Andreas


On 19 May 2011 03:31, Mark S. Miller <[hidden email]> wrote:
>
>
> On Wed, May 18, 2011 at 6:29 PM, Brendan Eich <[hidden email]> wrote:
>>
>> On May 18, 2011, at 5:57 PM, Bob Nystrom wrote:
>>
>> class Point {
>>   public x = 0, y = 0;
>> }
>> let p = new Point();
>> p.x; // 0
>>
>> This is pretty rare, in my experience. A hard case? If the constructor
>> does set x and y from parameters, then you have double-initialization. If
>> some properties are non-writable, you can't do this. YAGNI?
>
> +1. If you're gonna initialize them somewhere, why not always do so in the
> constructor and avoid special cases?
>
>>
>> /be
>
>
>
> --
>     Cheers,
>     --MarkM
>
> _______________________________________________
> es-discuss mailing list
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss
>
>



--
    Cheers,
    --MarkM

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Dave Herman
In reply to this post by Andreas Rossberg-4
Yes, we've talked about this. One of the issues I don't know how to resolve is if we want to allow the specification of class properties aka statics, then those need *not* to be in the scope of the constructor arguments, which ends up with very strange scoping behavior:

    var x = "outer"
    class C(x) {
        static foo = x // "outer" -- whoa!
    }

I'm not 100% up on the current thinking of the group that's been working on classes, and whether they are including statics in the design, but I think they are.

Dave

On May 19, 2011, at 5:36 AM, Andreas Rossberg wrote:

> My apologies if this has been discussed to death before -- well,
> actually, I'd be surprised if it hasn't (pointers would be welcome).
>
> I think it is worth noting that the baroque notation for defining
> constructors that we see in the C++ / Java / C# world primarily is an
> artefact of the desire to allow multiple constructors with overloading
> in those languages. We don't have that issue in JS, so I wonder why we
> cannot go for something more elegant? There is precedent in other
> OOPLs (off the top of my head, e.g. Scala and OCaml) for putting the
> constructor arguments on the class head directly, and executing the
> class body like a block when the constructor is invoked. AFAICS:
>
> -- This approach is significantly slimmer (and, I'd argue, more
> readable) than the discussed alternatives, without needing any
> keywords:
>
> class Point(x0, y0) {
>  public x = x0
>  public y = y0
> }
>
> -- It naturally allows what Bob was suggesting:
>
> class Point {  // no argument list would be shorthand for (), just
> like when invoking new
>  public x = 0
>  public y = 0
> }
>
> -- It avoids additional hoops with initializing const attributes:
>
> class ImmutablePoint(x0, y0) {
>  const x = x0  // just like elsewhere
>  const y = y0
> }
>
> -- The constructor arguments naturally are in the scope of the entire
> object, so often you do not even need to introduce explicit (private)
> fields to store them:
>
> class Point(x, y) {
>  public function abs() { return Math.sqrt(x*x, y*y) }
> }
>
> /Andreas
>
>
> On 19 May 2011 03:31, Mark S. Miller <[hidden email]> wrote:
>>
>>
>> On Wed, May 18, 2011 at 6:29 PM, Brendan Eich <[hidden email]> wrote:
>>>
>>> On May 18, 2011, at 5:57 PM, Bob Nystrom wrote:
>>>
>>> class Point {
>>>   public x = 0, y = 0;
>>> }
>>> let p = new Point();
>>> p.x; // 0
>>>
>>> This is pretty rare, in my experience. A hard case? If the constructor
>>> does set x and y from parameters, then you have double-initialization. If
>>> some properties are non-writable, you can't do this. YAGNI?
>>
>> +1. If you're gonna initialize them somewhere, why not always do so in the
>> constructor and avoid special cases?
>>
>>>
>>> /be
>>
>>
>>
>> --
>>     Cheers,
>>     --MarkM
>>
>> _______________________________________________
>> es-discuss mailing list
>> [hidden email]
>> https://mail.mozilla.org/listinfo/es-discuss
>>
>>
> _______________________________________________
> es-discuss mailing list
> [hidden email]
> https://mail.mozilla.org/listinfo/es-discuss

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Brendan Eich-3
In reply to this post by Mark S. Miller-2
On May 19, 2011, at 6:36 AM, Mark S. Miller wrote:

> 1) Starting from scratch, there's no problem engineering a VM to make objects-as-closures efficient, especially given the semi-static analysis that class proposal was designed to enable. However, JS VM implementors are not starting from scratch. Fitting such a new optimization into existing heavily optimized engines was thought to be a hard sell. Especially since all major VM implementors would need to agree.

The hardship comes in part from mutability and fresh identity per constructor call of all the closure-methods. Specifying these as immutable and joined, optimizing their references to constructor parameters and per-instance variables to avoid capturing each constructor call's environment, would help.


> 2) The conventional JS pattern is to place methods on the prototype, not the instance, and many felt that the main thing classes need to provide is a syntax to make this traditional semantics easier to express.

This is the big one.

/be

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Bob Nystrom
In reply to this post by Brendan Eich-3


On Wed, May 18, 2011 at 6:29 PM, Brendan Eich <[hidden email]> wrote:
On May 18, 2011, at 5:57 PM, Bob Nystrom wrote:

class Point {
  public x = 0, y = 0;
}

let p = new Point();
p.x; // 0

This is pretty rare, in my experience.

I just did some spelunking through some JS code. In about a dozen classes, I found 88 per-instance properties. Of them, 34 were initialized without reference to this or a ctor parameter, so about 39% of the properties could be expressed using the above syntax.

C# and Java both support this and I use it heavily.

A hard case? If the constructor does set x and y from parameters, then you have double-initialization.

Well, if you are going to initialize it in the ctor, I wouldn't bother doing so outside of it too. I'm not proposing that an initializer is required, just that it's allowed. This does a few things for me:

1. It lets me make it obvious which instance state is ctor-dependent and which isn't. This is fairly important to me because I find it makes it easier to understand a class's state.

2. It's consistent with the rest of the member declarations which allow initializers:

class Foo {
  public onInstance = 0;
         onPrototype = 0;
  static onCtor = 0;
}

Seems weird to me that the first line would be an error when the other two aren't.

3. It's consistent with Java and C# which both allow this. Not that we need to mimic those languages, but it doesn't hurt to make the most of our users' expectations.

4. It's terse:

class Stack {
  public items;
  constructor() {
    this.items = [];
  }
  ...
}

class Stack {
  public items = [];
  ...
}


- bob

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Brendan Eich-3
On May 19, 2011, at 10:44 AM, Bob Nystrom wrote:

On Wed, May 18, 2011 at 6:29 PM, Brendan Eich <[hidden email]> wrote:
On May 18, 2011, at 5:57 PM, Bob Nystrom wrote:

class Point {
  public x = 0, y = 0;
}

let p = new Point();
p.x; // 0

This is pretty rare, in my experience.

I just did some spelunking through some JS code. In about a dozen classes, I found 88 per-instance properties. Of them, 34 were initialized without reference to this or a ctor parameter, so about 39% of the properties could be expressed using the above syntax.

Don't take this the wrong way ;-), but how many of those initializations were immediately overwritten by the constructor, unconditionally or even conditionally?


C# and Java both support this and I use it heavily.

A hard case? If the constructor does set x and y from parameters, then you have double-initialization.

Well, if you are going to initialize it in the ctor, I wouldn't bother doing so outside of it too. I'm not proposing that an initializer is required, just that it's allowed. This does a few things for me:

1. It lets me make it obvious which instance state is ctor-dependent and which isn't. This is fairly important to me because I find it makes it easier to understand a class's state.

Good point.


2. It's consistent with the rest of the member declarations which allow initializers:

class Foo {
  public onInstance = 0;
         onPrototype = 0;
  static onCtor = 0;
}

Seems weird to me that the first line would be an error when the other two aren't.

Ditto.


3. It's consistent with Java and C# which both allow this. Not that we need to mimic those languages, but it doesn't hurt to make the most of our users' expectations.

Meh :-P.


4. It's terse:

class Stack {
  public items;
  constructor() {
    this.items = [];
  }
  ...
}

class Stack {
  public items = [];
  ...
}

You'd need a fresh [] evaluation per construction. Mark pointed out how this seems to change the evaluation rules for the immediate elements of the class body. Not fatal but another kind of inconsistency, along a different dimension.

/be


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Bob Nystrom


On Thu, May 19, 2011 at 10:50 AM, Brendan Eich <[hidden email]> wrote:
Don't take this the wrong way ;-), but how many of those initializations were immediately overwritten by the constructor, unconditionally or even conditionally?

Heh, zero, actually. We're pretty scrupulous here. The 34 examples fell roughly into these buckets:

1. State that just always started a certain way: this.checked = false;
2. Collections that are mutated, but never assigned: this.items = [];
3. Objects used to compose a larger one: this.myButton = new Button("label");

If it was ever assigned to in the ctor using state passed in, or in a control flow path that was dependent on ctor arguments (or this), I didn't count it in the 34.

4. It's terse:

class Stack {
  public items;
  constructor() {
    this.items = [];
  }
  ...
}

class Stack {
  public items = [];
  ...
}

You'd need a fresh [] evaluation per construction. Mark pointed out how this seems to change the evaluation rules for the immediate elements of the class body. Not fatal but another kind of inconsistency, along a different dimension.

I agree, this part feels a little weird. The fact that we're doing anything per-instance in the class body is probably the strangest part of this proposal since the other two objects touched by the class body (the ctor object and the prototype) are both singletons. Even declaring per-instance properties there feels a little strange, but I think it's generally worth that inelegance. <shrug>

- bob


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Brendan Eich-3
On May 19, 2011, at 11:02 AM, Bob Nystrom wrote:

On Thu, May 19, 2011 at 10:50 AM, Brendan Eich <[hidden email]> wrote:
Don't take this the wrong way ;-), but how many of those initializations were immediately overwritten by the constructor, unconditionally or even conditionally?

Heh, zero, actually. We're pretty scrupulous here. The 34 examples fell roughly into these buckets:

1. State that just always started a certain way: this.checked = false;
2. Collections that are mutated, but never assigned: this.items = [];
3. Objects used to compose a larger one: this.myButton = new Button("label");

If it was ever assigned to in the ctor using state passed in, or in a control flow path that was dependent on ctor arguments (or this), I didn't count it in the 34.

Thanks for measuring, helpful beyond our qualitative arguments.

Here's another question: how many property initializations could be done straight from the parameter of the same name?

And here's the punchline: we are forgetting about parameter default values:


Apart from the desire to declare per-instance property names, do we really need default values if the constructor author could write default parameter values?

class Point {
  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }
 ...
}


Now, Scala and CoffeeScript fans will object to repeating x and y three times. IIRC an earlier proposal even allowed for the concise syntax

class Point {
  constructor(this.x = 0, this.y = 0) {
  }
 ...
}

The CoffeeScript syntax is even sweeter:

class Point {
  constructor(@x = 0, @y = 0);
 ...
}

The objection here is that @ is coveted, for private names or decorators.

Perhaps we could use leading dot in formal parameter names to mean this.:

class Point {
  constructor(.x = 0, .y = 0);
 ...
}

Comments welcome!


4. It's terse:

class Stack {
  public items;
  constructor() {
    this.items = [];
  }
  ...
}

class Stack {
  public items = [];
  ...
}

You'd need a fresh [] evaluation per construction. Mark pointed out how this seems to change the evaluation rules for the immediate elements of the class body. Not fatal but another kind of inconsistency, along a different dimension.

I agree, this part feels a little weird. The fact that we're doing anything per-instance in the class body is probably the strangest part of this proposal since the other two objects touched by the class body (the ctor object and the prototype) are both singletons. Even declaring per-instance properties there feels a little strange, but I think it's generally worth that inelegance. <shrug>

See if my parameter default value pitch does not provide a better alternative.

/be


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Mark S. Miller-2


On Thu, May 19, 2011 at 11:47 AM, Brendan Eich <[hidden email]> wrote:
On May 19, 2011, at 11:02 AM, Bob Nystrom wrote:

On Thu, May 19, 2011 at 10:50 AM, Brendan Eich <[hidden email]> wrote:
Don't take this the wrong way ;-), but how many of those initializations were immediately overwritten by the constructor, unconditionally or even conditionally?

Heh, zero, actually. We're pretty scrupulous here. The 34 examples fell roughly into these buckets:

1. State that just always started a certain way: this.checked = false;
2. Collections that are mutated, but never assigned: this.items = [];
3. Objects used to compose a larger one: this.myButton = new Button("label");

If it was ever assigned to in the ctor using state passed in, or in a control flow path that was dependent on ctor arguments (or this), I didn't count it in the 34.

Thanks for measuring, helpful beyond our qualitative arguments.

Here's another question: how many property initializations could be done straight from the parameter of the same name?

And here's the punchline: we are forgetting about parameter default values:


Apart from the desire to declare per-instance property names, do we really need default values if the constructor author could write default parameter values?

class Point {
  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }
 ...
}


Now, Scala and CoffeeScript fans will object to repeating x and y three times. IIRC an earlier proposal even allowed for the concise syntax

class Point {
  constructor(this.x = 0, this.y = 0) {
  }
 ...
}

I like Bob's "this.x" form best, especially when combined with your observation about default values.

The reason I prefer "this.x" rather than the terser ".x" or "@x" (even assuming away conflicts with other uses if "@"), is that I can imagine many programmers with ES < 6 experience, on seeing a "this.x" parameter for the first time, to be able to guess what it means.




 

The CoffeeScript syntax is even sweeter:

class Point {
  constructor(@x = 0, @y = 0);
 ...
}

The objection here is that @ is coveted, for private names or decorators.

Perhaps we could use leading dot in formal parameter names to mean this.:

class Point {
  constructor(.x = 0, .y = 0);
 ...
}

Comments welcome!


4. It's terse:

class Stack {
  public items;
  constructor() {
    this.items = [];
  }
  ...
}

class Stack {
  public items = [];
  ...
}

You'd need a fresh [] evaluation per construction. Mark pointed out how this seems to change the evaluation rules for the immediate elements of the class body. Not fatal but another kind of inconsistency, along a different dimension.

I agree, this part feels a little weird. The fact that we're doing anything per-instance in the class body is probably the strangest part of this proposal since the other two objects touched by the class body (the ctor object and the prototype) are both singletons. Even declaring per-instance properties there feels a little strange, but I think it's generally worth that inelegance. <shrug>

See if my parameter default value pitch does not provide a better alternative.

/be




--
    Cheers,
    --MarkM

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Brendan Eich-3
On May 19, 2011, at 11:55 AM, Mark S. Miller wrote:

> I like Bob's "this.x" form best, especially when combined with your observation about default values.

That's great -- if we want to shorthand harder, we can consider .x for this.x down the road.


> The reason I prefer "this.x" rather than the terser ".x" or "@x" (even assuming away conflicts with other uses if "@"), is that I can imagine many programmers with ES < 6 experience, on seeing a "this.x" parameter for the first time, to be able to guess what it means.

Sure.

So I'm not sure you bought my argument that this means we don't need intiialisers for the public and private name declarations that are outside of the constructor head and body. And I don't want to twist Bob's arms. Let me know if this *does* relieve us from having to support initialisers evaluated per-construction yet declared outside the constructor.

/be

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Mark S. Miller-2


On Thu, May 19, 2011 at 12:02 PM, Brendan Eich <[hidden email]> wrote:
On May 19, 2011, at 11:55 AM, Mark S. Miller wrote:

> I like Bob's "this.x" form best, especially when combined with your observation about default values.

That's great -- if we want to shorthand harder, we can consider .x for this.x down the road.


> The reason I prefer "this.x" rather than the terser ".x" or "@x" (even assuming away conflicts with other uses if "@"), is that I can imagine many programmers with ES < 6 experience, on seeing a "this.x" parameter for the first time, to be able to guess what it means.

Sure.

So I'm not sure you bought my argument that this means we don't need intiialisers for the public and private name declarations that are outside of the constructor head and body. And I don't want to twist Bob's arms. Let me know if this *does* relieve us from having to support initialisers evaluated per-construction yet declared outside the constructor.

Even without this, I am opposed to initializers outside the constructor for the reason you already mentioned: confusion of execution time issues. If we allowed

    class Stack {
      public items = [];
      //...
    }

some experienced JS programmers without Java experience would look at this and sensibly expect the [] to be evaluated once per evaluation of the class definition, leading all stacks to initialize their own "items" variable to a shared initially empty list. For some purposes, this might even be useful. Others would sensibly expect it to be evaluated once per constructor call. Any resolution will give some sensible programmers rude surprises.

With the ability to say "this.x = 0" in the constructor parameters, this choice becomes painless. But IMO the choice is necessary regardless.

--
    Cheers,
    --MarkM

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Axel Rauschmayer
In reply to this post by Brendan Eich-3
> Yes, we've talked about this. One of the issues I don't know how to resolve is if we want to allow the specification of class properties aka statics


This is a somewhat orthogonal issue:

Currently the implementation of a type is a constructor function. This always felt slightly odd to me, because the prototype seems the more obvious choice. Then instanceof would be simpler. Furthermore, type properties (class properties) would make more sense and could additionally be inherited from supertype to subtype (use case: a root type implements a method for subtyping that can be applied to itself to create the first subtype).

The changes would be as follows:

(1) Allow |new| not just for functions, but also for "type objects". The type object itself would be the prototype of each newly created instance, instance data would be added in a special method (e.g. "constructor" or "new").
(2) Allow "type objects" to be the rhs for instanceof (in addition to function). That is, if the rhs of instanceof is an object, look for it in the prototype chain.

Has this been considered?

Then (the similar) "classes" (as syntactic sugar for constructor functions etc.) would maybe not be needed. Still missing is convenient subtyping

The remaining missing functionality is subtyping, but that could be added as an API (let me know if I should give an example, it would look a lot like YUI, but with less impedance mismatch).

--
Dr. Axel Rauschmayer

[hidden email]
twitter.com/rauschma

home: rauschma.de
blog: 2ality.com



_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Bob Nystrom
In reply to this post by Brendan Eich-3


On Thu, May 19, 2011 at 11:47 AM, Brendan Eich <[hidden email]> wrote:
Here's another question: how many property initializations could be done straight from the parameter of the same name?

Of the 88 fields I looked at, 10 were straight copies of a constructor argument. Another 4 are copies with an "if undefined then use this default instead" check. That may be biased a bit low since most of the code I looked at is UI widget-like stuff. Code that's more data-model oriented would probably have more cases where a class is just wrapping a bunch of arguments.

Apart from the desire to declare per-instance property names, do we really need default values if the constructor author could write default parameter values?

class Point {
  constructor(x = 0, y = 0) {
    this.x = x;
    this.y = y;
  }
 ...
}

That covers a different (though useful!) case. This would be helpful for the 4 cases above where a field is initialized to a constructor argument if given, or to a default value if not. For the 34 other cases, those fields don't come from the constructor arguments at all.
 
class Point {
  constructor(this.x = 0, this.y = 0) {
  }
 ...
}

Yeah, that's in the document the proposal is linking to now (https://docs.google.com/document/d/1gOFRSBOKtB8VjXC5LRhhNiGi1n1QQ6z1O6LyxjrOB3g/edit?hl=en_US). I'd like to roll that into the proposal as an optional refinement. I really really like this.
 
The objection here is that @ is coveted, for private names or decorators.

FWIW, my vote would be to keep that for decorators/annotations.
 
Perhaps we could use leading dot in formal parameter names to mean this.:

class Point {
  constructor(.x = 0, .y = 0);
 ...
}

Comments welcome!

That's delightful!

- bob


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Mark S. Miller-2

sent from android
On May 19, 2011 3:09 PM, "Bob Nystrom" <[hidden email]> wrote:
>
>
>
> On Thu, May 19, 2011 at 11:47 AM, Brendan Eich <[hidden email]> wrote:
>>
>> Here's another question: how many property initializations could be done straight from the parameter of the same name?
>
>
> Of the 88 fields I looked at, 10 were straight copies of a constructor argument. Another 4 are copies with an "if undefined then use this default instead" check. That may be biased a bit low since most of the code I looked at is UI widget-like stuff. Code that's more data-model oriented would probably have more cases where a class is just wrapping a bunch of arguments.
>
>> Apart from the desire to declare per-instance property names, do we really need default values if the constructor author could write default parameter values?
>>
>> class Point {
>>   constructor(x = 0, y = 0) {
>>     this.x = x;
>>     this.y = y;
>>   }
>>  ...
>> }
>
>
> That covers a different (though useful!) case. This would be helpful for the 4 cases above where a field is initialized to a constructor argument if given, or to a default value if not. For the 34 other cases, those fields don't come from the constructor arguments at all.
>  
>>
>> class Point {
>>   constructor(this.x = 0, this.y = 0) {
>>   }
>>  ...
>> }
>
>
> Yeah, that's in the document the proposal is linking to now (https://docs.google.com/document/d/1gOFRSBOKtB8VjXC5LRhhNiGi1n1QQ6z1O6LyxjrOB3g/edit?hl=en_US). I'd like to roll that into the proposal as an optional refinement. I really really like this.

Please do. I like it too. But yes, as an optional refinement.

>>
>> The objection here is that @ is coveted, for private names or decorators.
>
>
> FWIW, my vote would be to keep that for decorators/annotations.
>  
>>
>> Perhaps we could use leading dot in formal parameter names to mean this.:
>>
>> class Point {
>>   constructor(.x = 0, .y = 0);
>>  ...
>> }
>>
>> Comments welcome!
>
>
> That's delightful!
>
> - bob
>


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

RE: I noted some open issues on "Classes with Trait Composition"

Luke Hoban
In reply to this post by Brendan Eich-3
>> 2) The conventional JS pattern is to place methods on the prototype, not the instance, and many felt that the main thing classes need to provide is a syntax to make this traditional semantics easier to express.

>This is the big one.

Class syntax with class parameters instead of explicit constructors can still be defined to produce methods on the prototype, right?

Andreas' example:

  class Point(x, y) {
   public function abs() { return Math.sqrt(x*x, y*y) }
  }

Can effectively desguar to:

  function Point(x,y) {
    this.x = x;
    this.y = y;
  }
  Point.prototype.abs = function() { return Math.sqrt(this.x*this.x, this.y*this.y); }

Or, with private names, to a variant with the captured x and y stored as private fields on the instance.  It's really just a syntax choice between explicit constructor functions and parameterized class definitions.  There are many benefits to the latter, and many modern OO languages seem to be using this style effectively.  

Luke

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Brendan Eich-3
In reply to this post by Mark S. Miller-2
On May 19, 2011, at 2:10 PM, Mark S. Miller wrote:

On May 19, 2011 3:09 PM, "Bob Nystrom" <[hidden email]> wrote:

>> class Point {
>>   constructor(this.x = 0, this.y = 0) {
>>   }
>>  ...
>> }
>
>
> Yeah, that's in the document the proposal is linking to now (https://docs.google.com/document/d/1gOFRSBOKtB8VjXC5LRhhNiGi1n1QQ6z1O6LyxjrOB3g/edit?hl=en_US). I'd like to roll that into the proposal as an optional refinement. I really really like this.

Please do. I like it too. But yes, as an optional refinement.

+1

And I'm +1 on including it normatively, FWIW.

You guys have been good sports, here is one last minor comment: The ExportableDefinition nonterminal produces this right-hand side:
      Identifier = Expression ;                            // provided data
which works perfectly after class (now static, hrm, two minor comments then ;-). But without a keyword in front,

class Point {
  x = 0;
  ...
}

is a bit of a mystery.

Is it an assignment expression-statement? No, no statements as ClassElements.

Could it be creating a prototype property, as other unprefixed property initialisers such as get and set accessor declarations, and property initialiser extensions declaring prototype methods, do? Yes, that's the intent.

But, I object (mildly), it does not look like a property initialiser. It looks like an assignment expression-statement.

I see the dilemma: if you use colon, you walk into class body as object initialiser body, with comma separation, etc. If you use equal sign, you avoid that.

But this is only for defining data properties on the class prototype. Is this case common? I can't think of a built-in class that has such a data property. It's very rare.

So how about YAGNI -- leave this out, it won't be needed enough and one can always write C.prototype.x = 0 or Object.defineProperty(C.prototype, 'x', ...) after the class declaration has been evaluated.

Or if you have a measured need based on some existing code, which I'd love to hear about: consider a prefixing keyword to make this look like an alternative to static x = 0.

Ok, second minor point. At least dherman and I really dug the use of class where static is now specified. We avoid unwanted baggage from other (and static as in typing) languages. We point directly to the value of the binding created by the class declaration or evaluated from an anonymous class declaration: the constructor.

Any strong reason why this change was made?

Thanks for considering,

/be


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Mark S. Miller-2
On Thu, May 19, 2011 at 3:08 PM, Brendan Eich <[hidden email]> wrote:
[...]

You guys have been good sports, here is one last minor comment: The ExportableDefinition nonterminal produces this right-hand side:
      Identifier = Expression ;                            // provided data
which works perfectly after class (now static, hrm, two minor comments then ;-). But without a keyword in front,

class Point {
  x = 0;
  ...
}

is a bit of a mystery.

Is it an assignment expression-statement? No, no statements as ClassElements.

Could it be creating a prototype property, as other unprefixed property initialisers such as get and set accessor declarations, and property initialiser extensions declaring prototype methods, do? Yes, that's the intent.

But, I object (mildly), it does not look like a property initialiser. It looks like an assignment expression-statement.

I see the dilemma: if you use colon, you walk into class body as object initialiser body, with comma separation, etc. If you use equal sign, you avoid that.

But this is only for defining data properties on the class prototype. Is this case common? I can't think of a built-in class that has such a data property. It's very rare.

So how about YAGNI -- leave this out, it won't be needed enough and one can always write C.prototype.x = 0 or Object.defineProperty(C.prototype, 'x', ...) after the class declaration has been evaluated.

Or if you have a measured need based on some existing code, which I'd love to hear about: consider a prefixing keyword to make this look like an alternative to static x = 0.

Bob initially used "var" for that purpose. I do not object to having such a short prefixing keyword. But ever since the "let is the new var" revolution, whenever my eye notices a "var", my immediate reaction is "Danger danger warning warning. Once we can, figure out what this code is doing and rewrite as using 'let' or 'const'." Once ES-next is real and people really can get rid of "var"s, I hope this reaction will become more common. Looking for bad old code to fix? Grep for "var". Anything that habituates us to "var", reducing this alarm reaction, seems bad. 

That said, if we decide we really want a keyword here and can't come up with a better alternative than "var", I probably could be talked into it. And I'm happy to see this added as an option to the strawman.

 

Ok, second minor point. At least dherman and I really dug the use of class where static is now specified. We avoid unwanted baggage from other (and static as in typing) languages. We point directly to the value of the binding created by the class declaration or evaluated from an anonymous class declaration: the constructor.

Any strong reason why this change was made?

When I first saw this use of "class" I loved it. I never liked "static" and I disliked it even more after that. But the following syntactic case convinced me otherwise:

    class A {
      class B {...}        // defines A.prototype.B as a class
      class class C {...}  // defines A.C as a class
      class D() {...}      // defines A.D as a function.
    }

by contrast, I don't think the following creates any similar confusion

    class A {
      class B {...}        // defines A.prototype.B as a class
      static class C {...} // defines A.C as a class
      static D() {...}     // defines A.D as a function.
    }


 

Thanks for considering,

/be




--
    Cheers,
    --MarkM

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Brendan Eich-3
On May 19, 2011, at 3:43 PM, Mark S. Miller wrote:

Or if you have a measured need based on some existing code, which I'd love to hear about: consider a prefixing keyword to make this look like an alternative to static x = 0.

Bob initially used "var" for that purpose. I do not object to having such a short prefixing keyword. But ever since the "let is the new var" revolution, whenever my eye notices a "var", my immediate reaction is "Danger danger warning warning. Once we can, figure out what this code is doing and rewrite as using 'let' or 'const'." Once ES-next is real and people really can get rid of "var"s, I hope this reaction will become more common. Looking for bad old code to fix? Grep for "var". Anything that habituates us to "var", reducing this alarm reaction, seems bad. 

I note that Declaration is produced by ExportableDefinition produced by PublicPrototypePropertyDefinition. That means let, const, function, and nested class are all possible in a class body.

http://wiki.ecmascript.org/doku.php?id=strawman:classes_with_trait_composition#member_declarations_and_definitions is light on details, but I took these to define local bindings usable in other ClassElements -- not to create prototype properties (or any kind of property on an object).

Did I miss something?


That said, if we decide we really want a keyword here and can't come up with a better alternative than "var", I probably could be talked into it. And I'm happy to see this added as an option to the strawman.

I'm still not sure we need to bind (enumerable by default, I just noticed -- a for-in hazard) data properties on the class prototype. If we do, I agree that var is not a good keyword to retask. We could make new keywords in context in the class body, I think. Dave mooted proto when we were talking about this. Just an idea.


When I first saw this use of "class" I loved it. I never liked "static" and I disliked it even more after that. But the following syntactic case convinced me otherwise:

    class A {
      class B {...}        // defines A.prototype.B as a class
      class class C {...}  // defines A.C as a class
      class D() {...}      // defines A.D as a function.
    }


Oh, so nested class declaration binds a prototype property? That is confusing. What do let, const, and function do? The other ClassElement production right-hand sides produce accessor and the new method property initialiser forms, not Declarations.


by contrast, I don't think the following creates any similar confusion

    class A {
      class B {...}        // defines A.prototype.B as a class
      static class C {...} // defines A.C as a class
      static D() {...}     // defines A.D as a function.
    }

Given a nested class declaration binding a prototype property, I see your point. But is that nested class semantic really the right one, and how does it related to the other Declaration variants?

/be


_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Mark S. Miller-2
In reply to this post by Luke Hoban


On Thu, May 19, 2011 at 3:00 PM, Luke Hoban <[hidden email]> wrote:
>> 2) The conventional JS pattern is to place methods on the prototype, not the instance, and many felt that the main thing classes need to provide is a syntax to make this traditional semantics easier to express.

>This is the big one.

Class syntax with class parameters instead of explicit constructors can still be defined to produce methods on the prototype, right?

Andreas' example:

 class Point(x, y) {
  public function abs() { return Math.sqrt(x*x, y*y) }
 }

Hi Luke, given the above code, the semantics I should expect is that abs lexically captures the x and y in scope. Notice that abs nowhere makes any mention of "this", so I wouldn't expect it to be "this" sensitive. "this" sensitivity is one of the trickiest hazards of JavaScript, but so far it is only raised by functions that explicitly mention this.

Say have a point pt and wish to store its abs method in some data structure of ours, for purpose of calling it later:

    var absFuncs = [];

    function storeAbsFrom(pt) {
      absFuncs.push(pt.abs);
    }

    function getTotalAbs1() {
      let result = 0;
      for (let i = 0, len = absFuncs.length; i < len; i++) {
        result += absFuncs[i]();
      }
      return result;
    }
  
    function getTotalAbs2() {
      return absFuncs.reduce(0, function(sum, abs) {
        return sum + abs();
      });
    }

If abs lexically captured x and y, both of the above getTotalAbs functions would work. Instead, with the suggested semantics, getTotalAbs1() returns NaN and getTotalAbs2() throws a TypeError.
 

Can effectively desguar to:

 function Point(x,y) {
   this.x = x;
   this.y = y;
 }
 Point.prototype.abs = function() { return Math.sqrt(this.x*this.x, this.y*this.y); }

Or, with private names, to a variant with the captured x and y stored as private fields on the instance.  It's really just a syntax choice between explicit constructor functions and parameterized class definitions.  There are many benefits to the latter, and many modern OO languages seem to be using this style effectively.

Most other OO languages don't have the this-confusion hazards that JavaScript suffers from.
 

Luke




--
    Cheers,
    --MarkM

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Mark S. Miller-2
In reply to this post by Brendan Eich-3
On Thu, May 19, 2011 at 3:56 PM, Brendan Eich <[hidden email]> wrote:
On May 19, 2011, at 3:43 PM, Mark S. Miller wrote:

Or if you have a measured need based on some existing code, which I'd love to hear about: consider a prefixing keyword to make this look like an alternative to static x = 0.

Bob initially used "var" for that purpose. I do not object to having such a short prefixing keyword. But ever since the "let is the new var" revolution, whenever my eye notices a "var", my immediate reaction is "Danger danger warning warning. Once we can, figure out what this code is doing and rewrite as using 'let' or 'const'." Once ES-next is real and people really can get rid of "var"s, I hope this reaction will become more common. Looking for bad old code to fix? Grep for "var". Anything that habituates us to "var", reducing this alarm reaction, seems bad. 

I note that Declaration is produced by ExportableDefinition produced by PublicPrototypePropertyDefinition. That means let, const, function, and nested class are all possible in a class body.

Yes.

 

http://wiki.ecmascript.org/doku.php?id=strawman:classes_with_trait_composition#member_declarations_and_definitions is light on details, but I took these to define local bindings usable in other ClassElements -- not to create prototype properties (or any kind of property on an object).

Did I miss something?

Yes. An unannotated Declaration defines a prototype property of that name. 

    class Point {
      const x = 0;
      //...
    }

defines a non-writable, enumerable, non-configurable "x" property on Point.prototype, as if by

    Object.defineProperty(Point.prototype, 'x', {
      value: 0, writable: false, enumerable: true, configurable: false
    });


 


That said, if we decide we really want a keyword here and can't come up with a better alternative than "var", I probably could be talked into it. And I'm happy to see this added as an option to the strawman.

I'm still not sure we need to bind (enumerable by default, I just noticed -- a for-in hazard) data properties on the class prototype.

I agree that data on the prototype is comparatively rare. But symmetry and consistency sometimes lead us to supporting rare cases, merely to avoid introducing special cases that prohibit them.

 
If we do, I agree that var is not a good keyword to retask. We could make new keywords in context in the class body, I think. Dave mooted proto when we were talking about this. Just an idea.

I prefer "proto" to "var", so I could probably be talked into that as well.

 


When I first saw this use of "class" I loved it. I never liked "static" and I disliked it even more after that. But the following syntactic case convinced me otherwise:

    class A {
      class B {...}        // defines A.prototype.B as a class
      class class C {...}  // defines A.C as a class
      class D() {...}      // defines A.D as a function.
    }


Oh, so nested class declaration binds a prototype property? That is confusing. What do let, const, and function do?

The same thing. An unprefixed let defines a writable enumerable configurable property on the prototype. An unprefixed const as above defines a non-writable, enumerable, non-configurable property on the prototype. An unprefixed class or function defines a non-enumerable ...

All of these, as well as the other ExportableDefinitions, define corresponding props on the constructor when prefixed by something -- currently "static".

 
The other ClassElement production right-hand sides produce accessor and the new method property initialiser forms, not Declarations.

As a ClassElement, "x = y;" is just shorthand for "let x = y;". Likewise "static x = y;" is just shorthand for "static let x = y;".

 


by contrast, I don't think the following creates any similar confusion

    class A {
      class B {...}        // defines A.prototype.B as a class
      static class C {...} // defines A.C as a class
      static D() {...}     // defines A.D as a function.
    }

Given a nested class declaration binding a prototype property, I see your point. But is that nested class semantic really the right one, and how does it related to the other Declaration variants?

Symmetric and consistent without special cases.

Also, Gilad Bracha has shown many cool patterns in Newspeak leveraging the ability to inherit a default nested class but being able to override it by a like-named nested class in a subclass. Once you get used to it, it's actually much more natural and expressive than static nested classes.
 

/be




--
    Cheers,
    --MarkM

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
Reply | Threaded
Open this post in threaded view
|

Re: I noted some open issues on "Classes with Trait Composition"

Bob Nystrom
In reply to this post by Brendan Eich-3

On Thu, May 19, 2011 at 3:08 PM, Brendan Eich <[hidden email]> wrote:
class Point {
  x = 0;
  ...
}

is a bit of a mystery.

Is it an assignment expression-statement? No, no statements as ClassElements.

Could it be creating a prototype property, as other unprefixed property initialisers such as get and set accessor declarations, and property initialiser extensions declaring prototype methods, do? Yes, that's the intent.

But, I object (mildly), it does not look like a property initialiser. It looks like an assignment expression-statement.

Agreed, completely. My first stab at this used var for declaring fields:

class Point {
  var x = 0; // x field on prototype
}

I know Mark wishes var could be banished from the language completely. My goal would be to have let become the thing you use for variables and var then becomes a "free" keyword we can then repurpose for something like this. I don't know if that would just sow confusion, but var seems like too nice of a keyword to go to waste, and it would be unambiguous inside a class since a class body doesn't allow arbitrary statements.
 
But this is only for defining data properties on the class prototype. Is this case common?

Closure code uses data-on-the-prototype pervasively for default values, like:

class Thermostat {
  var temp = 70; // on proto
  setTemp(temp) {
    this.temp = temp; // once you touch it, it becomes specific to the instance
  }
}

I count nine examples of this just in Control.js (search for "prototype.content_"). It has the advantage of not wasting per-instance memory for values that are still the default, which is a neat trick that class-based languages can't do.

Or if you have a measured need based on some existing code, which I'd love to hear about: consider a prefixing keyword to make this look like an alternative to static x = 0.

That was part of my motivation for using var. It seemed weird to have an adjective like static without a noun that it modifies. (I was also using function for methods for the same reason, though we've moved away from that.)

- bob

_______________________________________________
es-discuss mailing list
[hidden email]
https://mail.mozilla.org/listinfo/es-discuss
1234