Proposal for faster this assignments in constructor functions
Le 28 nov. 2018 à 19:32, Simo Costa <andrysimo1997 at gmail.com> a écrit :
In costructor functions and in the constructor() method in ES6 classes is easily to fall in the following pattern:
F(par1, par2, ..., parN) { this.par1 = par1; this.par2 = par2; ... this.parN = parN; }
So my proposal is to avoid those repetitions by prefixing a dot . to each parameter: F(.par1, .par2, ..., .parN) {}
Simple but quite useful. More info here: jfet97/proposal-fast-this-assignments, jfet97/proposal-fast-this-assignments
Simple, but a brand new and nonobvious syntax, for a relatively limited use case.
Also, just one dot is too inconspicuous. At the very least, I’d prefer:
function F(this.par1, this.par2, this.par3) { }
whose meaning is somewhat more intuitive.
Also noteworthy: in many cases, you can already reduce repetition with the combination of Object.assign
, improved syntax literal, and imagination; see:
@Claude
Your suggestion is still too much verbose/ripetitive in my opinion because
you repeat the this
keyword. I agree with the "limited use cases" of my
proposal but not with your concerns about the syntax.
Anyway I am open for improvements about it.
I do not think that it could bring more mistuderstading than the
introduction of the rest/spread syntax has brought.
And about the Object.assign
solution, it isn't bad...but there are always (theoretically) an
object creation and a function call, as well as the repetition of the
parameters.
F(par1, par2) {
Object.assign(this, {par1, par2});
}
What about this:
F({par1, par2, ..., parN}) {
Object.assign(this, arguments[0]);
}
No. It has to be arguments[0]. Notice the braces around the argument list?
This is meant to use destructured arguments. The argument list in this case
has only 1 element, hence the [0]
.
Ops I saw it now ahahah sorry. Anyway there is a function call and an "useless" object creation. Furthermore you are forced to pass arguments using an object (not so bad but...another object creation).
To avoid the function call I would do:
F({par1, par2, ..., parN}) {
this = {...arguments[0]};
}
Another thing...arguments
is deprecated, right?
Arguments is only deprecated in strict mode.
F(arg) {
let {par1, par2, ..., parN} = arg;
Object.assign(this, arg);
}
This version works in strict mode, but it's less clear what parameters
should be passed. I used Object.assign
because I assumed that F
was
called with new
, providing a valid this
object. Otherwise, your
original example would either throw in strict mode, or add your parameters
to "global", unless it was called with Function.prototype.call
.
I assumed that F
was called with new
, providing a valid this
object
too and I've used the object spread syntax to avoid a function call.
Anyway I prefer
F(par1, par2, ..., parN) {
this.par1 = par1;
this.par2 = par2;
...
this.par3 = par3;
}
to this:
F(arg) {
let {par1, par2, ..., parN} = arg;
Object.assign(this, arg);
}
Because there is no function call (Object.assign
), no useless object
creation (when you pass arguments) nor duplication. The first should be
faster. And it is obviously clearer.
So:
F(.par1, .par2, ..., .parN) {}
Could be fast too. Maybe also clear, because when you learn what it does...you cannot misundertood it later.
In the ~maybe long~ future , after the current decorators proposal [1], we can start thinking about a Method Parameter Decorator (already proposed [2]), we could do something like:
class Foo {
constructor(@field foo) {
}
}
In my opinion, it would be a much more powerful approach Em qua, 28 de nov de 2018 às 16:33, Simo Costa <andrysimo1997 at gmail.com> escreveu:
I forgot the links in my last email, here they are: [1] tc39/proposal-decorators [2] docs.google.com/document/d/1Qpkqf_8NzAwfD8LdnqPjXAQ2wwh8BBUGynhn-ZlCWT0 Em qua, 28 de nov de 2018 às 20:48, Augusto Moura <augusto.borgesm at gmail.com> escreveu:
Just dropping in real quick to correct a couple things.
First, arguments
itself is not deprecated in strict mode. Things
like arguments.caller
are deprecated and throw a TypeError
when
the corresponding function is in strict mode.
Second, some of you all were looking to use Object.assign
to help
explain. You'd need to do this for it to be correct:
class C {
constructor(a, b, ..., y, z) {
Object.assign(this, {a, b, ..., y, z})
}
}
(Not TC39, just I felt the need to correct people here.)
Isiah Meadows contact at isiahmeadows.com, www.isiahmeadows.com
Dart has something like this: www.dartlang.org/guides/language/language-tour#constructors
class Point {
num x, y;
// Syntactic sugar for setting x and y
// before the constructor body runs.
Point(this.x, this.y);
}
I could see a form of BindingElement (et al) that allows this
or this
.
BindingIdentifier:
class Point {
constructor(this.x, this.y) {}
}
new Point(10, 20).x; // 10
class Foo {
constructor(this.x, { y: this.z, …this }) {}
}
const f = new Foo(1, { y: 2, w: 3 });
f.x; // 1
f.z; // 2
f.w; // 3
TypeScript has something similar for constructors in the form of “parameter property assignments”, but they are triggered by the presence of an modifier (i.e. public
, private
, protected
, readonly
).
Ron
From: es-discuss <es-discuss-bounces at mozilla.org> On Behalf Of Claude Pache
Sent: Wednesday, November 28, 2018 11:46 AM To: Simo Costa <andrysimo1997 at gmail.com>
Cc: es-discuss at mozilla.org Subject: Re: Proposal for faster this assignments in constructor functions
Le 28 nov. 2018 à 19:32, Simo Costa <andrysimo1997 at gmail.com<mailto:andrysimo1997 at gmail.com>> a écrit :
In costructor functions and in the constructor() method in ES6 classes is easily to fall in the following pattern:
F(par1, par2, ..., parN) {
this.par1 = par1;
this.par2 = par2;
...
this.parN = parN;
}
So my proposal is to avoid those repetitions by prefixing a dot . to each parameter:
F(.par1, .par2, ..., .parN) {}
Simple but quite useful. More info here: jfet97/proposal-fast-this-assignmentsna01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fjfet97%2Fproposal-fast-this-assignments&data=02|01|ron.buckton%40microsoft.com|259306c32d9d4c9d053308d6556a246a|72f988bf86f141af91ab2d7cd011db47|1|0|636790311735674930&sdata=8fEkmw%2BDL94JClmv9jv7c13okC7Myd4kxCNNSNjA1LE%3D&reserved=0
Simple, but a brand new and nonobvious syntax, for a relatively limited use case.
Also, just one dot is too inconspicuous. At the very least, I’d prefer:
function F(this.par1, this.par2, this.par3) { }
whose meaning is somewhat more intuitive.
Also noteworthy: in many cases, you can already reduce repetition with the combination of Object.assign
, improved syntax literal, and imagination; see:
2ality.com/2014/12/es6-oop.html#use-cases-for-objectassignna01.safelinks.protection.outlook.com/?url=http%3A%2F%2F2ality.com%2F2014%2F12%2Fes6-oop.html%23use-cases-for-objectassign&data=02|01|ron.buckton%40microsoft.com|259306c32d9d4c9d053308d6556a246a|72f988bf86f141af91ab2d7cd011db47|1|0|636790311735684942&sdata=1L%2F6iyFqjV7EWd51XhKUWGUnKTrGCW%2FXU6o4RAQESVA%3D&reserved=0
-1
this makes the common javascript-painpoint of pinpointing bugs in UX-workflows even worse.
you're getting invalid visualization or timeouts from the web-UI during integration/qa. maybe it's from a bug in following low-level code? if so, was bug from a) constructor, b) foo, or c) @field?
class Foo { constructor(@field foo) { } } var bar = new Foo(foo);
this is also why i'm against decorators in general. i've used them in python, and beyond toy-cases, it quickly becomes very confusing whether I should put my business-logic in the decorator or the function ... and pity to any poor-soul debugging my code trying to figure it out, or worse ends up having to refactor the decorator (without breaking any of the functions it touches).
just to clarify, my previous -1 was to Augusto's decorator-suggestion.
but I'm against the the main proposal as well due extra cognitive-load of decoding the "." prefix when debugging code not written by me. I already have readability problems with {...} brackets due to destructuring, es-classes, and fat-arrows.
Thanks for partecipating to this discussion and for your opinions on my proposal. I cannot reply to each one of you now, but I'd like to receive feedback also on the Further Ideas section. Maybe you'll find something interesting.
In JS there is no way (please correct me if you find the way) to gather all arguments directly in an object (without passing directly an object obv) like you can easily do with an array.
So, to mantain this way to pass the arguments:
f(arg1, arg2, arg3);
you could do like that to store them into an array:
function f(...args){
/* code */
}
but something like that won't work if you want to gather them into an obj:
function f({...obj}){
/* code */
}
My proposal could solve this problem.
P.S.
Though you pass an object created on the fly (and this is wath I'd like to avoid):
f( { arg1, arg2, arg3 } );
to make that work:
function f({...obj}){
/* code */
}
you cannot do something like that directly:
function f ( { arg1: obj.arg1, arg2: obj2.arg2, arg3: obj2.arg3 }) {
// ...
}
My proposal theoretically could solve this "problem" too.
P.P.S
function f( arg1, arg2, arg3 ) {
const obj = {arg1, arg2};
const obj2 = {arg3};
//...
}
You obv could do that, but there are avoidable repetitions:
function f( obj.arg1, obj.arg2, obj2.arg3 ) {
//...
}
And maybe something like:
function f( obj.{arg1, arg2}, obj2.arg3 ) {
//...
}
could be faster :P
When or more than one parameters should be inserted into an object we could do like that:
f(.{par1, par2, parN}) {}
equivalent to:
f(this.{par1, par2, parN}) {}
that is equivaent to:
f(.par1, .par2, .parN) {}
and:
f(this.par1, this.par2, this.parN) {}
SO we could write that:
f(obj.par1, obj.par2, obj2.parN) {}
in this way:
f(obj.{par1, par2}, obj2.parN) {}
On Wed, Nov 28, 2018 at 6:33 PM Simo Costa <andrysimo1997 at gmail.com> wrote:
So my proposal is to avoid those repetitions...
I'm a bit surprised to find that no one's mentioned Bob Myers' proposal for pick notation yet, which would readily address this requirement and various other requirements beyond initializing newly-constructed objects.
Using Bob's current draft syntax, Simo's example would be:
constructor(par1, par2, par3) {
this.{par1, par2, par3} = {par1, par2, par3};
}
or if the constructor accepts an object:
constructor(options) {
this.{par1, par2, par3} = options;
}
But I think we can go further if we tweak the syntax a bit. Perhaps:
constructor(par1, par2, par3) {
this.{} = {par1, par2, par3};
}
...where the property names are inferred from the properties on the right-hand side. That would be functionally equivalent to:
constructor(par1, par2, par3) {
Object.assign(this, {par1, par2, par3});
}
...but since the syntax is clear about the intent, when an object initializer (rather than just object reference) is used on the right-hand side it's an optimization target if a constructor is "hot" enough to justify it (e.g., an engine could optimize it into individual assignments).
For me, that would be a great, clear, concise feature, and hits the other use cases Bob mentions in his proposal. I like that I'm still in control of what parameters get assigned as properties (which I think has been true of all of the suggestsions in this thread).
-- T.J. Crowder
Thank you T.J. Crowder for giving me your opinion on this proposal. I didn't know about the Bob Myers' for pick notation and it isn't bad.
I still prefer something like that:
constructor(this.{par1, par2, par3}) {
}
but this doesn't sound bad to me:
constructor(par1, par2, par3) {
this.{} = {par1, par2, par3};
}
There is still a repetition, but it is a a step forward.
On Fri, Nov 30, 2018 at 1:52 PM Simo Costa <andrysimo1997 at gmail.com> wrote:
There is still a repetition, but it is a a step forward.
Yeah. :-| For me, for public properties, it's good enough, but I hear you. It's a lot of repetition for private fields, though.
Just FWIW by way of prior art, it's probably worth noting TypeScript's approach, though I'm fairly certain it won't be adopted by JavaScript:
class Example {
constructor(
public foo: string,
private bar: number
) {
}
showFoo() {
console.log(e1.foo);
}
showBar() {
console.log(e1.bar);
}
}
const e1 = new Example("answer", 42);
e1.showFoo(); // "answer"
e1.showBar(); // 42
The public
and private
in the parameter list both declare them as
members of the type and auto-initialize them from the parameters. (That
private
is TypeScript's version of private, not JavaScript's -- the
member is not part of the type's public interface, just its private one, so
the TypeScript compiler can error on inappropriate use, but the transpiled
JavaScript has them as normal properties.)
The equivalent (but with real private fields) with the class fields proposal would be:
class Example {
foo;
#bar;
constructor(foo, bar) {
this.foo = foo;
this.#bar = bar;
}
showFoo() {
console.log(e1.foo);
}
showBar() {
console.log(e1.#bar);
}
}
const e1 = new Example("answer", 42);
e1.showFoo(); // "answer"
e1.showBar(); // 42
So, that's typing foo
and bar
four types rather than once (in
TypeScript). (Okay, we could have skipped the foo
declaration and only
typed foo
three times; but you have to declare private fields, so it's
four for #bar
/bar
.)
Continuing the train of thought and taking inspiration from TypeScript and Bob's proposal, this could be a follow-on if Bob's progresses (which I'd very much like to see):
class Example {
constructor(this.{foo, #bar}) {
}
showFoo() {
console.log(e1.foo);
}
showBar() {
console.log(e1.#bar);
}
}
const e1 = new Example("answer", 42);
e1.showFoo(); // "answer"
e1.showBar(); // 42
That construct would both declare (if not already declared) and initialize. You could repeat it as necessary. For instance, if your constructor really needs a non-property as its first and fourth parameters and properties/fields as its second, third, and fifth:
class Example {
constructor(first, this.{second, #third}, fourth, this.{fifth}) {
// presumably code here uses `first` and `fourth`
}
}
which is
class Example {
second;
#third;
fifth;
constructor(first, this.{second, #third}, fourth, this.{fifth}) {
this.second = second;
this.#third = third;
this.fifth = fifth;
// presumably code here uses `first` and `fourth`
}
}
Not a parameter list I'd design without a really, really good reason, but...
Food for thought.
-- T.J. Crowder
On Fri, Nov 30, 2018 at 5:49 PM T.J. Crowder <tj.crowder at farsightsoftware.com> wrote:
which is
class Example { ... }
Apologies, that "which is" at the end of my previous message should have been:
class Example {
second;
#third;
fifth;
constructor(first, second, third, fourth, fifth) {
this.second = second;
this.#third = third;
this.fifth = fifth;
// presumably code here uses `first` and `fourth`
}
}
Editing error. :-)
-- T.J. Crowder
this.{} = {par1, par2, par3};
Of course, that could be expressed as ```this = { ...this, par1, par2, par3
}```, but that's still a bit verbose.
I already know this is going to get dismissed for changing the way the `+`
operator works, but it strikes me that a better way of expressing that
might be ```this += { par1, par2, par3 }```.
That naturally leads to having ```obj1 + obj2 === Object.assign(obj1,
obj2)``` and ```arr1 + arr2 === (arr1 = [ ...arr1, ...arr2 ])```, which
would be good except for the whole breaking-change thing.
@T.J. Crowder No problems for the error :P. Anyway I know Typescript's approach but I prefer something like:
class Example {
constructor(this.{foo, #bar}) {
}
showFoo() {
console.log(this.foo);
}
showBar() {
console.log(this.#bar);
}
}
const e1 = new Example("answer", 42);
e1.showFoo(); // "answer"
e1.showBar(); // 42
To both declare, if not already declared, and initialize public and private properties.
@Michael Luder-Rosefield
this += { par1, par2, par3 }
I do love it!!!
But, for now, reassigning to this
is forbidden by javascript.
``this = { ...this, par1, par2, par3 }```
Oh, wait, I'm going to point out the problem with what I wrote before
anyone else does: this will lose any non-enumerable properties on this
,
which in a class instance is kind of a big no-no. Object.assign
is better
here.
Any reason we can't consider a scala-like constructor?
class Point(x, y) {
toString() {
return `(${this.x},${this.y})`
}
}
console.log(`??? ${new Point(1, 2)}`); // ??? 1,2
Doesn't conflict with existing syntax, it's clear, and you can still shorthand most data-classes or structs to just:
class MyData(some, props, here) {}
I imagine this as the syntax:
class Point(x, y) { // implicits x, y as first two args of constru
constructor(a, b, c) {
this.z = a + b + c;
console.log(this.x, this.y, this.c); // values of: a, b, c
console.log(this.x === a, this.y === b); // true, true
}
}
And de-sugared:
class Point {
constructor(a, b, c) {
this.x = a;
this.y = b;
// rest of body
this.z = a + b + c;
}
}
Your imagined syntax would suggest to me that the constructor had 5 arguments, not 3 - it seems strange to me to have "x" and "a" be implicitly two ways to refer to the same thing.
I can see that, although I'm struggling to come to that conclusion if I saw
that block of code fresh. I don't think it would be immediately clear,
though. In your interpretation of "this has 5 arguments", what would you
expect them to be? constructor(x, y, a, b, c)
? Just prepend them?
I'm trying to work with the idea of the existing constructor function and a possible shorthand. Another problem with almost any suggestion is how it may interact with destructuring and expectations (with any additions of "bind these").
I am highly suspect that any solution that is both ergonomic and concise could pass.
What about making an explicit constructor
function invalid in the case of
the shorthand syntax?
class Point(x, y) {
...
}
// As shorthand for:
class Point {
constructor(x, y){
this.x = x;
this.y = y;
}
}
If this kind of syntax can't fly, that's fine, but I feel there isn't going to be an ergonomic way to have a simple way to bind constructor arguments. Although that makes things like super calls potentially awkward, we can also assume that if you extend a class that it calls super in the generated constructor. I'm not offended by a short hand as my last post, as the transition is quite natural if you need a more complex use case:
class Point(x, y) {
toString() { ... }
}
// Oh no, now I have complex logic!
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
...
}
toString() { ... }
}
In costructor functions and in the constructor() method in ES6 classes is easily to fall in the following pattern:
So my proposal is to avoid those repetitions by prefixing a dot
.
to each parameter:Simple but quite useful. More info here: jfet97/proposal-fast-this-objects-assignments
[EDIT]
It could be a shortcut for:
F(this.par1, this.par2, ..., this.parN) {}
so we could create and initialize objects in a more general situation:
equivalent to:
f(par1, par2, parN) { let or const obj = { par1, par2 }; let or const obj2 = { parN }; }
[EDIT 2]
We can go a step further. When more than one parameters has to be inserted into an object:
we could write: