Proposal for faster this assignments in constructor functions

# Simo Costa (6 years ago)

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-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:

f(obj.par1, obj.par2, obj2.parN) {}

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:

f(obj.par1, obj.par2, obj.parN) {}

we could write:

f(obj.{par1, par2, parN}) {}
# Claude Pache (6 years ago)

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:

2ality.com/2014/12/es6-oop.html#use

# Simo Costa (6 years ago)

@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});
}
# Ranando King (6 years ago)

What about this:

F({par1, par2, ..., parN}) {
Object.assign(this, arguments[0]);
}
# Simo Costa (6 years ago)

@Ranando

I think you meant:

F({par1, par2, ..., parN}) {
Object.assign(this, arguments);
}

This is briefly explained on github, anyway you'll get an undesired length property.

Il giorno mer 28 nov 2018 alle ore 22:20 Ranando King <kingmph at gmail.com>

ha scritto:

# Ranando King (6 years ago)

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

# Simo Costa (6 years ago)

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?

# Ranando King (6 years ago)

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.

# Simo Costa (6 years ago)

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.

# Augusto Moura (6 years ago)

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:

# Augusto Moura (6 years ago)

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:

# Isiah Meadows (6 years ago)

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

# Ron Buckton (6 years ago)

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

# kai zhu (6 years ago)

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

# kai zhu (6 years ago)

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.

# Simo Costa (6 years ago)

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

# Simo Costa (6 years ago)

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) {}

# T.J. Crowder (6 years ago)

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

# Simo Costa (6 years ago)

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.

# T.J. Crowder (6 years ago)

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

# T.J. Crowder (6 years ago)

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

# Michael Luder-Rosefield (6 years ago)

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.
# Simo Costa (6 years ago)

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

# Simo Costa (6 years ago)

@Michael Luder-Rosefield

this += { par1, par2, par3 }

I do love it!!!

But, for now, reassigning to this is forbidden by javascript.

# Michael Luder-Rosefield (6 years ago)

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

# dante federici (6 years ago)

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;
  }
}
# Jordan Harband (6 years ago)

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.

# dante federici (6 years ago)

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() { ... }
}