tc39/proposal-optional-chaining

Should “optional new” be implemented?

claudepache opened this issue · 11 comments

That is:

new Foo?.()

The ongoing refactoring of Issue #20 will not allow that construct at first, because that would add complications in the grammar, and it is practically unused in CoffeeScript (#17). So, it is probably not worth.

However, If you have strong use cases for new, please share them

Apart the absence of use cases, I have the following additional arguments against it:

  • A complication in the mental model for short-circuiting due to non-strict left-to-right evaluation order of new. The simple rule for short-circuiting is: “if the LHS of the operator is null, do not evaluate its RHS”. However, in expressions like new a?.() and new a?.b(), if a is null, the new operator will not be evaluated, even if the keyword is found on the left of ?.
  • Technically, a complication in the grammar, due to the coexistence of argument-less constructors new a and function invocations b() with similar precedence. That would force to duplicate the rules for OptionalExpression, in the same way that the current spec has both MemberExpression and CallExpression.

So, I’m closing this issue, deciding to drop support for new. But if you have objections, you can still share them in comments.

@claudepache Sorry to bring up an old discussion that didn't seem to get much attention, but:

Feel free to tell me if this makes no sense, but if there's a use case for function invocation:

func?.(); // is the func non-null? call it.

Just from a naive point of view, there would also be a use case for creating an instance with new (as they're both kind of just function calls):

new Class?.(); // is the Class non-null? Create an instance.

However, I can definitely understand if this is not supported due to new undefined throwing TypeError: undefined is not a constructor (in the case where Class is null/undefined).


On the other hand, with the introduction of null coalescing, there's this possible case (admittedly a weird way of writing it):

// flag.js
const someCondition = 1 === 1;

module.exports = () => someCondition; // export some condition that evaluates to true
// NonNullish.js
const flag = require('./flag')();

class NonNullish {
// ...
}

module.exports = flag ? NonNullish : null; // if flag is set, export NonNullish
// Nullish.js
const flag = require('./flag')();

class Nullish {
 //...
}

module.exports = flag ? null : Nullish; // if flag is set, don't export Nullish
// instance.js
const NonNullish = require('./NonNullish');
const Nullish = require('./Nullish');

let instance;
if (Nullish != null)
  instance = new Nullish();
else
  instance = new NonNullish();

// when flag is set to true:
instance instanceof NonNullish // true
// when flag is set to false:
instance instanceof Nullish // true

The example ended up being a bit more lengthy than I first envisioned it, sorry. 😂


But with support for the new keyword and null coalescing, we could replace:

let instance;
if (Nullish != null)
  instance = new Nullish();
else
  instance = new NonNullish();

with

const instance = new Nullish.?() ?? NonNullish();

On the other hand, I may feel more strongly than others on the importance of "uniformity of treatment" as you mentioned in Issue #10:

For the sake of uniformity of treatment, I think one should include what the spec calls “Left-Hand-Side Expression”, which includes additionally new and tagged templates (which are sort of method/function invocations), e.g. new a?b.c().d[x] `{y}`;

I actually feel more strongly about support for tagged template literals, but that is a discussion for another time/place.

Realized shortly after posting that you can currently write this:

const instance = new (Nullish || NonNullish)();

and I'd expect this to be possible w/ null coalescing:

const instance = new (Nullish ?? NonNullish)();

but I'd think this should also be available (perhaps a bit more idiomatic to some):

const instance = new Nullish?.() ?? NonNullish)();

const instance = new (Nullish || NonNullish)();

That's what I am currently doing:

throw new (self[type] || self.Error)(msg);

I didn't feel the need of anything fancy. Furthermore ?[] is not valid cf #5.

I didn't feel the need of anything fancy. Furthermore ?[] is not valid cf #5.

Sorry @Mouvedia, not sure what you mean. I realize there are problems with ?[ and ?(. How does that apply here?

I'm honestly not sure how you're using this, but it seems it's another use case! Would it be helpful for clarity if

throw new (self[type] || self.Error)(msg);

could also be written

throw new (self?.[type] ?? self?.Error ?? Error)(msg);

in case of self === undefined?

How does that apply here?

Because my example features brackets: self[type].

@ScottRudiger

Would it be helpful for clarity if

throw new (self[type] || self.Error)(msg);

could also be written

throw new (self?.[type] ?? self?.Error ?? Error)(msg);

in case of self === undefined?

Sure, it can. (There is no “optional new”, here.)


The use case for ”optional new” should be sufficiently strong in order to justify the significant complication needed to specify it.

Thanks, I feel better about this direction now. 👍

@ScottRudiger

Would it be helpful for clarity if

throw new (self[type] || self.Error)(msg);

could also be written

throw new (self?.[type] ?? self?.Error ?? Error)(msg);

in case of self === undefined?

Sure, it can. (There is no “optional new”, here.)

The use case for ”optional new” should be sufficiently strong in order to justify the significant complication needed to specify it.

What's the meaning of the double question marks ???

@noscripter

What's the meaning of the double question marks ???

See https://github.com/tc39/proposal-nullish-coalescing

@noscripter

What's the meaning of the double question marks ???

See tc39/proposal-nullish-coalescing

Thanks for your quick reply, I found it too.