u
Sorry for the strange subject, I have written "object literal based class too minimal? (was: Re: @name)" but Postbox Express somehow ate it.
Herby Vojčík wrote:
Brendan Eich wrote:
Definitely, but classes have bigger issues than private syntax, and have for a while. Class-side inheritance, body syntax, whether there should be any declarative public syntax, what nested classes mean, static or 'class' members -- that's a partial list from memory.
Minimal classes based on object literals could be done but seem too minimal. As Waldemar suggested and Allen has worked to develop, they tend to collapse into object literals (with smaller extensions).
I am interested in this. Do you believe such classes, which are based on object literal syntax are too minimal?
I'd have to see the proposal to say, but the general tendency observed by Waldemar is real: too minimal and there's no point. Too maximal and we can't agree. We need "Goldilocks classes" -- just the right temperature/amount.
There is lots of possibilities what syntax to choose.
Yes, too many possibilities. That's a problem in itself.
And, classes in JS are in fact just an object (a prototype) which can be described (or extended) by a literal(-extension) (plus a constructor, yes, but it is just a little decoration to it).
We don't even agree on prototype-first vs. constructor-first naming
Brendan Eich wrote:
Herby Vojčík wrote:
Brendan Eich wrote:
Definitely, but classes have bigger issues than private syntax, and have for a while. Class-side inheritance, body syntax, whether there should be any declarative public syntax, what nested classes mean, static or 'class' members -- that's a partial list from memory.
Minimal classes based on object literals could be done but seem too minimal. As Waldemar suggested and Allen has worked to develop, they tend to collapse into object literals (with smaller extensions).
I am interested in this. Do you believe such classes, which are based on object literal syntax are too minimal? I'd have to see the proposal to say, but the general tendency observed by Waldemar is real: too minimal and there's no point. Too maximal and we can't agree. We need "Goldilocks classes" -- just the right temperature/amount.
There is lots of possibilities what syntax to choose.
Yes, too many possibilities. That's a problem in itself.
Isn't this the reason why we should look over them and see which direction is acceptable?
And, classes in JS are in fact just an object (a prototype) which can be described (or extended) by a literal(-extension) (plus a constructor, yes, but it is just a little decoration to it).
We don't even agree on prototype-first vs. constructor-first naming -- that is, whether classes in JS are "in fact" or in someone's opinion just a prototype (exemplar) object, or a constructor function (which as usual has a .prototype).
I think this is already solved since in "new Foo" Foo is class (the principle of "new Foo" expression) as well as constructor (by implementation) at the same time.
I'd like to know what these solutions lack (why are they "too minimal"). I will sketch some of them (they will use existing features, like super expressions, <|, private name shortcut declaration; some of the features more liberally).
=====
- 'class ...}' as a sugar for 'function ...}.prototype': (liberal use of <| for (sugared) function declarations as well)
private arr;
class List (n) { this. at arr = n === +n ? new Array(n) : []; }.{ at (i) { i = +i; if (i>=0 && i<this. at arr.length) { return this. at arr[i]; } else throw "Out of bounds: "+i; } size () { return this. at arr.length; } }
Comma elision in object literals is problematic, see past threads.
If it is problematic, it will be changed. Never mind. Let the classes follow if they are based on that.
Here you .{ to extend the class prototype, which means class X(){} denotes a prototype not a constructor, but that's backward by analogy to function and it hurts the static/class-extension use-case.
It is not used as often as the prototype extension. Prototype extension is the base of the class definition.
No one has proposed class X(){} as a function-like form, but if someone
I did, amongst others, in one of the threads.
did it would have to evaluate to the constructor. At that point, what is the purpose of 'class' instead of 'function'?
Semantically readable shortcut. Same as 'private x' is for 'const x = Name.create()'
List.{ from (array) { var r = new this(); r. at arr = array; return r; } };
This looks like a class-side extension (from is a "static method", specifically an alternative constructor). So the class expression evaluates to the prototype, but the class name when used as an Identifier expression evaluates to the constructor? That's incoherent.
That is coherent with "new Foo" - 'Foo is the class' means 'new Foo returns new instance'.
I'm out of time but I don't think shooting up a hodgepodge of proposal-parts, which don't appear to hang together, helps. Classes are
Sorry. I did not do that. I posted the more-or-less complete proposals, by-example way. They are thought of. They're not mere parts.
And others (there are three of them) are different - there isn't the "incoherency" you mentioned in the next two.
not going to reach consensus this way. A gist focusing on one and only one approach would be better, and could be forked to show alternatives
Doesn't strike a chord with me. I really feel handing more versions in and letting them to be compared is good thing to do. If you do not have time now, I'd be glad if you would be possible to look over them and comment them later, not shun them after first apparent error.
in a diffable way. We went through that exercise last year, Jeremy
Well, these are really different (maybe 2 and 3 may be diffed, but 1 is different from them...).
Ashkenas kicked it off. As usual with classes, even the least maximal proposal was too big and had a few controversial novelties (not found in CoffeeScript, e.g.).
There are minimal uses of class keyword that reuse object-literal. Since there is demand for 'class', I'd summarized what minimal uses that blend well with language but can help those that come from classful environments as well. I don't think they have this 'too maximal to swallow' property, neither of them.
The question is more of 'is it possible to have minimal (but still useful) use of class in ES.next(.next)) or there is demand to use it heavily?'. I am for lightweight use (or none - I can live without it, but it helps).
Herby Vojčík wrote:
class List (n) { this. at arr = n === +n ? new Array(n) : []; }.{ at (i) { i = +i; if (i>=0 && i<this. at arr.length) { return this. at arr[i]; } else throw "Out of bounds: "+i; } size () { return this. at arr.length; } }
[snip...]
List.{ from (array) { var r = new this(); r. at arr = array; return r; } };
This looks like a class-side extension (from is a "static method", specifically an alternative constructor). So the class expression evaluates to the prototype, but the class name when used as an Identifier expression evaluates to the constructor? That's incoherent.
That is coherent with "new Foo" - 'Foo is the class' means 'new Foo returns new instance'.
Yes, but your first example, class List(n) {...} cited above at the very top, uses .{ to add what looks like prototype methods at and size. If class List(n){...} evaluates to the constructor then you're adding these to the constructor function, not to its prototype. You'd need
class List (n) { this. at arr = n === +n ? new Array(n) : []; }.prototype.{ at (i) { i = +i; if (i>=0 && i<this. at arr.length) { return this. at arr[i]; } else throw "Out of bounds: "+i; } size () { return this. at arr.length; } }
And of course to make this work when used as an expression that should evaluate to the constructor function, tack on a .constructor at the end.
Was this just a mistake or are you trying to have class List(n){...} evaluate to the prototype while List later evaluates to the constructor? I am still confused by what you wrote and believe it does not hang together.
And that takes us around the hermeneutic cycle again, which was my point about classes not reaching consensus easily or by shotgunning the design space. You may hit something but not bring down the prize bird: the right not-too-minimal and not-too-maximal classes.
Brendan Eich wrote:
That is coherent with "new Foo" - 'Foo is the class' means 'new Foo returns new instance'.
Yes, but your first example, class List(n) {...} cited above at the very top, uses .{ to add what looks like prototype methods at and size. If class List(n){...} evaluates to the constructor then you're adding these to the constructor function, not to its prototype. You'd need
class List (n) { this. at arr = n === +n ? new Array(n) : []; }.prototype.{ at (i) { i = +i; if (i>=0 && i<this. at arr.length) { return this. at arr[i]; } else throw "Out of bounds: "+i; } size () { return this. at arr.length; } }
And of course to make this work when used as an expression that should evaluate to the constructor function, tack on a .constructor at the end.
Was this just a mistake or are you trying to have class List(n){...} evaluate to the prototype while List later evaluates to the constructor? I am still confused by what you wrote and believe it does not hang together.
I am probably writing densely and you had little time. I have written at the beginning of 1.: 'class ...}' as a sugar for 'function ...}.prototype' (I put similar texts describing the idea to the header of 2. and 3. as well)
So, exactly, I wanted class List () { ... } work exactly the same as function List () { ... }.prototype - so List is the function but completion value is its prototype.
Which lets do the
class List (..) { ... }.{ // instance methods here }
which is the most used case, but needs
List.{ // static ones here }
and is not exactly fine for class-expressions. But 1. is very minimal, leaving hard cases to be harder, but since it is only slightly-modified function (and user can by the time already learned enough to understand that 'class' is just a sugar), user can very easily switch class keyword to function keyword when it fits better for the situation. This 'class as function except returning prototype' is really useful primarily for declaring classes (and as a black magic for learners until they are later told class is nothing special). Just hinting it as one alternative of minimal 'class' use. Maybe it inspires something.
The other ones use 'class' differently.
And that takes us around the hermeneutic cycle again, which was my point about classes not reaching consensus easily or by shotgunning the design space. You may hit something but not bring down the prize bird: the right not-too-minimal and not-too-maximal classes.
Maybe one of the alternatives (I like the 2. even when it needs do expressions when you want full-fledged class expression) can strike a chord with some of the readers.
- is super-minimal (even when it returns the prototype) as its plus.
Herby Vojčík wrote:
I am probably writing densely and you had little time. I have written at the beginning of 1.: 'class ...}' as a sugar for 'function ...}.prototype' (I put similar texts describing the idea to the header of 2. and 3. as well)
I get it, but it is not coherent.
A function binding form in JS can be refactored as an expression, and it evaluates to the same result that an Identifier PrimaryExpression denoting the bound name evaluates to: the function object. Not its .prototype when used as an expression, the function itself when denoted by its name.
This is what I meant by incoherent. You can make it "consistent" by some criteria but it does not match function declarations vs. expressions and that is a problem.
Brendan Eich wrote:
Herby Vojčík wrote:
I am probably writing densely and you had little time. I have written at the beginning of 1.: 'class ...}' as a sugar for 'function ...}.prototype' (I put similar texts describing the idea to the header of 2. and 3. as well)
I get it, but it is not coherent.
A function binding form in JS can be refactored as an expression, and it evaluates to the same result that an Identifier PrimaryExpression denoting the bound name evaluates to: the function object. Not its .prototype when used as an expression, the function itself when denoted by its name.
This is what I meant by incoherent. You can make it "consistent" by some criteria but it does not match function declarations vs. expressions and that is a problem.
I do not understand your concern. If you define the class keyword works same as function keyword in all aspects except the completion value (that is also return value of class expression) return the prototype, what incoherency is there? It behaves consistently all over.
"does not match function declaration vs. expression" is a matter of interpretation. I can say it matches because:
- same as function declaration, class declaration defines function with supplied name and body, able to [[Construct]] and with prototype object created appropriately
- same as function declaration/expression, class declaration/expression's comp[etion value is well defined, in function case it is always the function, in class case it is always the function's prototype.
It is explicitly written that "since 'var foo = (function bar() {...})' put bar function into foo, it is compulsory for 'var foo=(class bar() {...})' to also put bar function into foo, because both are function-like constructs"?
Herby Vojčík wrote:
what incoherency is there? It behaves consistently all over.
You are mixing coherent and consistent here. I explicitly distinguished them. Making a declarative form that looks like a function declaration have an expression form that evaluates differently is IMHO incoherent. It will confuse users and be a stumbling block when refactoring. It is not worth the confusion.
Brendan Eich wrote:
I am interested in this. Do you believe such classes, which are based on object literal syntax are too minimal? There is lots of possibilities what syntax to choose. And, classes in JS are in fact just an object (a prototype) which can be described (or extended) by a literal(-extension) (plus a constructor, yes, but it is just a little decoration to it).
I'd like to know what these solutions lack (why are they "too minimal"). I will sketch some of them (they will use existing features, like super expressions, <|, private name shortcut declaration; some of the features more liberally).
=====
private arr;
class List (n) { this. at arr = n === +n ? new Array(n) : []; }.{ at (i) { i = +i; if (i>=0 && i<this. at arr.length) { return this. at arr[i]; } else throw "Out of bounds: "+i; } size () { return this. at arr.length; } }
List.{ from (array) { var r = new this(); r. at arr = array; return r; } };
private writeArr, readOffset;
List <| class Queue () { super(); this. at writeArr = []; this. at readOffset = 0; }.{ at (i) { /* needs redefinition */ } size () { return super.size() - this. at readOffset + this. at writeArr.length; } push (elements) { return writeArr.push.apply(writeArr, elements); } shift () { //reads from arr; shifts []->writeArr->arr if empty } }
// Queue inherits from, needs no Queue.{...} if does not want something additional
Cons: class expression would not return the class Pros: integrated to the language, just a pure helper sugar
=====
private arr;
function List (n) { this. at arr = n === +n ? new Array(n) : []; }
class List { at (i) { i = +i; if (i>=0 && i<this. at arr.length) { return this. at arr[i]; } else throw "List: Out of bounds: "+i; } size () { return this. at arr.length; } } static { from (array) { var r = new this(); r. at arr = array; return r; } };
private writeArr, readOffset;
List <| function Queue () { super(); this. at writeArr = []; this. at readOffset = 0; }
class Queue { at (i) { /* needs redefinition */ } size () { return super.size() - this. at readOffset + this. at writeArr.length; } push (elements) { return writeArr.push.apply(writeArr, elements); } shift () { //reads from arr; shifts []->writeArr->arr if empty } }
// Queue inherits from, needs no static if it does not want something additional
Cons: Two pieces Pros: You can define "partial classes" which have their pieces defined at different places in code.
=====
private arr;
class List { constuctor (n) { this. at arr = n === +n ? new Array(n) : []; } at (i) { i = +i; if (i>=0 && i<this. at arr.length) { return this. at arr[i]; } else throw "Out of bounds: "+i; } size () { return this. at arr.length; } } static { from (array) { var r = new this(); r. at arr = array; return r; } };
private writeArr, readOffset;
List <| class Queue { constructor () { super(); this. at writeArr = []; this. at readOffset = 0; } at (i) { /* needs redefinition */ } size () { return super.size() - this. at readOffset + this. at writeArr.length; } push (elements) { return writeArr.push.apply(writeArr, elements); } shift () { //reads from arr; shifts []->writeArr->arr if empty } }
// Queue inherits from, needs no static if it does not want something additional
Pros: One-piece Cons: Maybe slightly magical for the hardcore (constructor)
=====
Again, what is weakness of these solution that there must be something heavier?
Thanks,