Proposal: Forced Chaining Operator "!."

# Tobias Buschor (4 years ago)

Since we now have the "Optional Chaninig Operator" , perhaps a "Forced Chaining Operator" would also be worth considering. I, personally, could use it:

let table;
table!.user!.id!.type = 'int'

will evaluate to:

let table;
if ( ! ( table instanceOf Object) ) table = {};
if ( ! ( table.user instanceOf Object) ) table.user = {};
if ( ! ( table.user.id instanceOf Object) ) table.user.id = {};
table.user.id.type = 'int';

Also to be noted: Sometimes a fallback to Object.create(null) or something other might be desirable. But since {} is syntactical sugar for Object.create(Object.prototype), this would be consistent.

# Thomas Shinnick (4 years ago)

You are describing Perl's autovivification feature. Also possible (in that syntax) for arrays and mixed object/array chains. I liked it, but many saw it as a footgun. There was even a compile time module to turn off the feature, if the coder wanted more caution. Having mentioned Perl I will assume this is DOA?

# Jacob Bloom (4 years ago)

Maybe it would be less footgunny to support autovivification in a more class-based way, like Python does?

class AutoVivArray extends Array {
  [Symbol.getMissing](identifier) {
    /* if we're here, identifier is not an ownProperty
     * and is nowhere on the prototype chain */
    this[identifier] = new Whatever();
    return this[identifier];
  }
}

Though I can't see how that's much more useful than Proxies besides saving you a little boilerplate

# Joe Eagar (4 years ago)

Anyone have ideas on more examples? It’s tempting to make a transpiler plugin to see how it works in practice, but I’d like to see more examples first.

# Jacob Bloom (4 years ago)

Is the Perl syntax opt-in like the proposed operator? Or does it happen on all accesses to nulls? If it's opt-in in JS, then it doesn't seem to me that it'd cause too much unexpected behavior, though it could be argued that it's ripe for abuse by new devs trying to avoid errors.

Something that might be a more generalized middle ground (and could later assist in transpiling the !. operator) is a "getsert" (?) method in the standard library that takes a default value and sets it on the parent object if that property is currently unset:

Object.getsert = (obj, identifier, defaultvalue) => {
  if (!(identifier in obj)) obj[identifier] = defaultvalue;
  return obj[identifier];
}

const table = {};
console.log('before getsert:', table.user); // undefined
console.log('during getsert:', Object.getsert(table, 'user', 5)); // 5
console.log('after getsert:', table.user); // 5

...I have concerns about such a method's usability though, since a getsert is far more verbose than a normal get. It'd be more convenient on Object.prototype (e.g. table.getsert('user', 5) ), but I assume that's a no-go.

Oh also, I think the proposed syntax would collide with TypeScript's non-null assertion operator www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator -- I don't know to what degree that's a concern when proposing new JS syntax

# Jacob Bloom (4 years ago)

(Sorry for the triple-post, I keep pondering this proposal) Come to think of it, you could do something pretty similar with the ??= operator from the logical assignments proposal:

(((table ??= {}).user ??= {}).id ??= {}).type = 'int';

The main difference being that it tests for nullishness instead of whether the LHS is a non-null object, but I think that's within the spirit of the original proposal. It also lets you set a custom default value (like the "getsert" function above). The shortfall of course is the accumulating parentheses

# Max Fechner (4 years ago)

How about something like

table{}.user{}.id{}.type = ‘int’

this syntax could be used for arrays, too:

table{}.userList[].push(‘Jane Doe’)

From: es-discuss <es-discuss-bounces at mozilla.org> on behalf of Jacob Bloom <mr.jacob.bloom at gmail.com>

Date: Monday, 27April, 2020 at 02:23 To: es-discuss <es-discuss at mozilla.org>

Subject: Re: Proposal: Forced Chaining Operator "!."

(Sorry for the triple-post, I keep pondering this proposal) Come to think of it, you could do something pretty similar with the ??= operator from the logical assignments proposal:

(((table ??= {}).user ??= {}).id ??= {}).type = 'int';

The main difference being that it tests for nullishness instead of whether the LHS is a non-null object, but I think that's within the spirit of the original proposal. It also lets you set a custom default value (like the "getsert" function above). The shortfall of course is the accumulating parentheses

On Sat, Apr 25, 2020 at 8:08 PM Jacob Bloom <mr.jacob.bloom at gmail.com<mailto:mr.jacob.bloom at gmail.com>> wrote:

Is the Perl syntax opt-in like the proposed operator? Or does it happen on all accesses to nulls? If it's opt-in in JS, then it doesn't seem to me that it'd cause too much unexpected behavior, though it could be argued that it's ripe for abuse by new devs trying to avoid errors.

Something that might be a more generalized middle ground (and could later assist in transpiling the !. operator) is a "getsert" (?) method in the standard library that takes a default value and sets it on the parent object if that property is currently unset:

Object.getsert = (obj, identifier, defaultvalue) => {
  if (!(identifier in obj)) obj[identifier] = defaultvalue;
  return obj[identifier];
}

const table = {};
console.log('before getsert:', table.user); // undefined
console.log('during getsert:', Object.getsert(table, 'user', 5)); // 5
console.log('after getsert:', table.user); // 5

...I have concerns about such a method's usability though, since a getsert is far more verbose than a normal get. It'd be more convenient on Object.prototype (e.g. table.getsert('user', 5) ), but I assume that's a no-go.

Oh also, I think the proposed syntax would collide with TypeScript's non-null assertion operator www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator -- I don't know to what degree that's a concern when proposing new JS syntax

On Sat, Apr 25, 2020 at 3:00 PM Joe Eagar <joeedh at gmail.com<mailto:joeedh at gmail.com>> wrote:

Anyone have ideas on more examples? It’s tempting to make a transpiler plugin to see how it works in practice, but I’d like to see more examples first. Thanks

On Sat, Apr 25, 2020 at 1:12 PM Jacob Bloom <mr.jacob.bloom at gmail.com<mailto:mr.jacob.bloom at gmail.com>> wrote:

Maybe it would be less footgunny to support autovivification in a more class-based way, like Python does?

class AutoVivArray extends Array {
  [Symbol.getMissing](identifier) {
    /* if we're here, identifier is not an ownProperty
     * and is nowhere on the prototype chain */
    this[identifier] = new Whatever();
    return this[identifier];
  }
}

Though I can't see how that's much more useful than Proxies besides saving you a little boilerplate

On Fri, Apr 24, 2020 at 3:23 PM Thomas Shinnick <tshinnic at gmail.com<mailto:tshinnic at gmail.com>> wrote:

You are describing Perl's autovivification feature. Also possible (in that syntax) for arrays and mixed object/array chains. I liked it, but many saw it as a footgun. There was even a compile time module to turn off the feature, if the coder wanted more caution. Having mentioned Perl I will assume this is DOA?

On Fri, Apr 24, 2020, 14:36 Tobias Buschor <tobias.buschor at shwups.ch<mailto:tobias.buschor at shwups.ch>> wrote:

Since we now have the "Optional Chaninig Operator" , perhaps a "Forced Chaining Operator" would also be worth considering. I, personally, could use it:

let table;
table!.user!.id!.type = 'int'

will evaluate to:

let table;
if ( ! ( table instanceOf Object) ) table = {};
if ( ! ( table.user instanceOf Object) ) table.user = {};
if ( ! ( table.user.id<http://table.user.id> instanceOf Object) ) table.user.id<http://table.user.id> = {};
table.user.id.type = 'int';

Also to be noted: Sometimes a fallback to Object.create(null) or something other might be desirable. But since {} is syntactical sugar for Object.create(Object.prototype), this would be consistent.

# Naveen Chawla (4 years ago)

Can someone confirm if the following nullish coalescing assignment operator proposal example would work for this, or would it produce some other kind of result?:

(((table ??= {}).user ??= {}).id ??= {}).type = 'int'

Regardless, I'm not a TC39 member but I would definitely be against forcing a type during assignment if "not already that type", as recommended in the original post. I think this could be a source of serious bugs in particular when an existing type structure has been miscalculated by the person using that "force type" operator. So regardless, I would prefer any shorthand to only assign to object/array etc. "if nullish". This would raise errors if already of an unexpected type, but I would much prefer that over forcing a new structure where an existing one already exists (and overwriting data).

# Tobias Buschor (4 years ago)

I think that would work, but the many nested brackets bother me.

Am Mo., 27. Apr. 2020 um 14:23 Uhr schrieb Naveen Chawla < naveen.chwl at gmail.com>:

# Ben Wiley (4 years ago)

Sorry to show up late but just wanted to point out that shipping this feature with the ! symbol would break TypeScript, which has this operator but only at compile time for forcing the compiler to believe that a nullable value will be non-null at the time of execution (assuming you have some information the compiler doesn't). It has no runtime effect of creating objects.

Le mar. 5 mai 2020 07 h 36, Tobias Buschor <tobias.buschor at shwups.ch> a écrit :