Class property parameters

Hello,

Having used the 'property parameter' feature available in TypeScript. When writing JavaScript I find myself reaching for it. This leads me to think it could make a nice addition to the language.

class Rectangle {
  constructor(this height, this width) {}
}

would be equivalent to

class Rectangle {
  constructor(height, width) {    
    this.height = height;
    this.width = width;
  }
}

What do people think?

5 Likes

How would this work with derived classes? Presumably the assignment not happen until super()?

In general, would height and this.height be available simultaneously? Would reassigning height change the value of this.height (magic), or not (potentially causing bugs)?

1 Like

HI Jordan,

Great questions.

At first I thought the approach/syntax could be:

class Rectangle {
  constructor(this.height, this.width) {}
}

// equivalent to:

class Rectangle { constructor() {
  [this.height, this.width] = arguments;
}}

Which would not create the height or width bindings. But, as I think you're alluding to, this becomes problematic with derived classes.

class Shape {
  constructor(this.area) {}
}

class Rectangle extends Shape {
   constructor(this.height, this.width) {
    super(this.height * this.width);
    // ReferenceError: Must call super constructor in derived class before accessing 'this'
  }
}

So this seems to suggest that the syntax does need to make the parameter available directly on the constructor scope first so it can be used with the super constructor, and only after the super constructor would the value be assigned to the instance property.

On reflection the this <identifier> syntax does seem ambiguous if someone would expect that reassigning the identifier to also assign to the property. As you say, assigning to both does seem magical and only matches the behaviour of function parameters and the arguments object in sloppy-mode. So it seems best for the two to not influence each other (this is how TypeScript also implements this feature). It would be interesting to study if this behaviour could increase the chance of bugs for those that assume different behaviour and receive no error message.

Potentially to disambiguate between the scope binding and the property assignment the syntax could be split as in the following example, where the scope binding is declared separately and opt-in. Inspired by https://github.com/zkat/proposal-as-patterns

class Rectangle extends Shape {
  constructor(this.height as height, this.width as width) {
    super(height * width);
  }
} 

Of course this is closer in verbosity to existing code. Only cutting the repetition of 'height' down from 4 to 3.

class Rectangle extends Shape {
  constructor(height, width) {
    super(height * width);
    this.height = height;
    this.width = width;
  }
} 

Which makes me prefer the original keyword syntax this height, this width. Which cuts the repetition of 'height' down from 4 to 2, providing (in my opinion) a greater net benefit.

class Rectangle extends Shape {
  constructor(this height, this width) {
    super(height * width);
  }
}
1 Like

You can achieve this with parameter decorators.

Hi @jun-sheaf,

With the current decorator's proposal parameter decorators are listed as a possibly future extension, but if they are added it does looks like that approach could also work.

// using a mix-in to run the initialisation
class Rectangle extends WithProps {
  constructor(@prop('height') height, @prop('width') width) {}
}

// or using the @init decorator
class Rectangle {
  constructor(@init prop('height') height, @init prop('width') width) {}
}

Using decorators does have the downside of being less concise, but the upside of not requiring new syntax for people and minifiers to learn. One other downside is that decorators seem unlikely to support adding private fields. Whereas dedicated syntax could be extended to support adding private fields:

class Rectangle {
  constructor(this #height, this #width) {}

  get area() { return this.#height * this.#width; }
}
  1. Why is this less concise?
    a. Along with decorators, reflect metadata is also being considered. Once both of them passes (Iā€™m being optimistic), someone could create an @this decorator.

  2. They will support private fields.

Why is this less concise?

I can not see any reference in the decorators or reflect-metadata proposals that suggests that the parameter identifier string would be passed. Would you have a link you can share?

They will support private fields.

In the current decorators proposal it says:

"Ability to declare new private fields" ... "This decorators proposal deliberately omits these features"

So what I was looking at was typescript's implementation (which is an older version of the proposal it seems). The current decorator proposal states it does not include private fields or parameter decorators.

Now since this is the case, why should the JS community accept a this decorator over parameter decorators (since your proposal is essentially that)? (to be honest, you would have to analyze the current issues regarding parameter decorators to come up with a decent argument)

IMO, this proposal is too small. You are proposing a fairly large change in the syntax tree for something that does what a simple decorator could do.

I think the proposed syntax is the better option than leaving it up to userland decorators because

(1) it creates conformity. If you read some code and find constructor(@assign a) you don't know what it does, and have to jump into the decorator. Having "one way to do it" makes code easier and thus faster to read, also when one writes some small snippets one does not have to import assign all the time (and property parameters are very common).

(2) I guess syntax is easier to optimize than decorators, so engines could easily turn new Clazz(a) into { __proto__: Clazz.prototype, a }. Not an expert there, but I think this is worth considering.

I totally agree to the proposed syntax, but couldn't we also have it for class methods too?
a(this b) then reads run this.b = b; once this is assignable. The usecases are not that common but I can still think of some, also it keeps the class syntax uniform.

2 Likes
  1. It does not create conformity whatsoever. The argument doesn't make sense. Parameter decorators can live in JSLand or userland.
  2. I build compilers. It is the exact same.

Most people will look up this and ask how it works. I highly doubt anyone who read this proposal understood how this would work instantly with regards to assignment to this.param or to param itself. In particular, if you do any assignment, this proposal becomes useless since it doesn't save lines.

It's been said before and I'll say again. You would have to analyze the current issues regarding parameter decorators to come up with a decent arguments. If there is a proposal that is higher in stage than the given one and is more general, it is highly like there is a discussion about a specialized case of the general one with relation to the given one.

Conformity:

If the decorator lives in userland then there could be only one implementation of it on npm that everyone uses and becomes familiar with, but alternatively there could be lots of libraries that provide it each calling it a different name resulting in no conformity.

If there is a keyword in the language to achieve it, then there would only be that one keyword so it would definitely have the same name in all projects.

Optimisation:

With the current decorators proposal it would depend if the @init initialisation moves forward, without this decorators can not directly affect the shape of the object, they would need to store the values in a separate object which is then looped over by a mixin class constructor. This indirection would be harder for a compiler to optimise. One of the reasons the decorators proposal has been delayed is because of performance concerns.

IDE support:
Another potential downside to implementing this feature as a decorator is the ability for IDEs to also understand the added field so auto-complete can be provided when accessing this. This is not impossible, but harder to achieve compared to dedicated syntax.

'this' keyword:
Maybe a different keyword would be better than using this to make it more search engine friendly.

1 Like

As stated previously, this proposal is too small. I can write a simple Babel plugin that does this and the proposal would die since anyone who wants to use this will either use TypeScript or use Babel.

That's what TS does in this equivalent code.

class Rectangle {
  constructor(public height, public width) {}
}

So there is precedent, even though personally I'm skeptical of the feature. (I feel proper ADTs closer to, say, Kotlin's data classes or Haskell's type constructors would be infinitely better for virtually all use cases than what's proposed here.)

3 Likes