Optional assignment operator

# Jacob Pratt (6 years ago)

I've been having this thought recently, after running across a potential use case in practice. There will likely be conditional accessors at some point in the future (with optional chaining), but there will not be conditional assignment.

My thought was to have the following: this.foo ?= params?.foo; which can be desugared to if (($ref = params?.foo) !== undefined) { this.foo = $ref; }

I would strictly check for undefined, rather than nullish, as anything other than undefined would indicate that a value is present that can be set. If no value is present (such as a missing key on an object), nothing would be set. A reference must be used for the general case, as the object being assigned (the RHS) could be a function or getter with side-effects.

Not sure if it should be ?= or =?, as it would look somewhat odd (IMO) for things like ?+= or +=?.

Initial thoughts? Jacob Pratt

# Darien Valentine (6 years ago)

My thought was to have the following: this.foo ?= params?.foo; which can be desugared to if (($ref = params?.foo) !== undefined) { this.foo = $ref; }

Are there any specific advantages to the new syntax you’re describing? Initially, it appears to me like a less powerful form of an existing syntactic feature — default initializers and binding patterns allow expressing the same logic:

const obj = {
  demo(params={}) {
    ({ foo: this.foo=this.foo } = params);

    console.log(
      `params was ${ JSON.stringify(params) }; ` +
      `this.foo is now ${ JSON.stringify(this.foo) }`
    );
  }
}

obj.demo({ foo: 1 });
obj.demo({})
obj.demo({ foo: 2 });
obj.demo();
obj.demo({ foo: 3 });
obj.demo({});
# Jacob Pratt (6 years ago)

Sure, you'd be making an assignment no matter what in that case, which would unnecessarily trigger the setter, possibly causing side effects.

const obj = {
  get x() {
    return _x;
  },

  set x(value) {
    console.log('setter called');
    _x = value;
  },

  demo(params = {}) {
    ({ x: this.x = this.x } = params);
  },
};

obj.demo({ x: 5 });
obj.demo({ x: undefined });

This will log twice, where it only needs to trigger it once.

jhpratt

# Darien Valentine (6 years ago)

True — though I’m not sure why concern for this case would merit new syntax. Have you found that to be a common source of problems? Usually setters are light / idempotent.

(Not opposed to the premise btw — just trying to hear more about what problems it would solve, especially since syntactic solutions usually have to be pretty high-value or else address something which can’t be solved any other way.)

# Jacob Pratt (6 years ago)

I personally have not found that to be a source of problems in this instance, though I do on occasion have side effects in setters, such as saving into a database, emitting data on a WebSocket instance, etc. They could be debounced, but that's not always a feasible option.

I'd be interested to hear what others have to say about this proposal, as this is (in all honesty) just something I came across and thought there might be a better solution. The alternative you proposed, though not identical, would work in the majority of cases. At least to me, it wasn't immediately clear what it does when looking at it. Of course I fully understand it, it's just something that might need a comment to explain.

jhpratt

# Sam Ruby (6 years ago)

On Wed, Jul 4, 2018 at 7:12 PM, Jacob Pratt <jhprattdev at gmail.com> wrote:

I've been having this thought recently, after running across a potential use case in practice. There will likely be conditional accessors at some point in the future (with optional chaining), but there will not be conditional assignment.

My thought was to have the following: this.foo ?= params?.foo; which can be desugared to if (($ref = params?.foo) !== undefined) { this.foo = $ref; }

I would strictly check for undefined, rather than nullish, as anything other than undefined would indicate that a value is present that can be set. If no value is present (such as a missing key on an object), nothing would be set. A reference must be used for the general case, as the object being assigned (the RHS) could be a function or getter with side-effects.

Not sure if it should be ?= or =?, as it would look somewhat odd (IMO) for things like ?+= or +=?.

Initial thoughts?

Perl and Ruby have "||=" and "&&=" operators. They don't strictly check for undefined in either language.

These operators are frequently used (in particular, "||="). I do miss them.

Looking at the node.js source:

$ find . -name '*.js' -type f | xargs egrep ' (\S+) = \1 || ' | wc -l 1416

$ find . -name '*.js' -type f | xargs egrep ' if (!(\S+)) \1 = ' | wc -l 497

Nearly 2K occurrences in one code base.

Jacob Pratt

  • Sam Ruby
# Isiah Meadows (6 years ago)

How about you all take a look here: tc39/proposal-logical-assignment

Originally, it was at tc39/proposal-nullish-coalescing, but here tc39/proposal-nullish-coalescing#1, it was

discussed and pushed off to the logical assignment proposal as it was also a short-circuiting operator, even though it wasn't really a boolean operator.


Isiah Meadows me at isiahmeadows.com, www.isiahmeadows.com

# Jacob Pratt (6 years ago)

This idea isn't quite the same as ||=, as that includes any falsey value, nor ??=, as that includes null. My thought was to be able to set a property if there is a value that exists (which would include null, but not undefined).

It's certainly similar — should this be considered for addition to that existing proposal?

My thinking was along the lines of "Set this if there is something to set. If not, just keep going". It can be done with an if statement, but would require a temporary reference due to getters and/or method calls.

jhpratt

# Isiah Meadows (6 years ago)

You could file an issue there, where it's more likely to get seen by interested parties. The basic concept is basically reversed augmented assignment, where a @= b usually means a = a @ b, you'd want something like a =@ b meaning a = b @ a. (Operator is up for debate, and =- conflicts with negation.)

I could see the use of it - ?? is the most common case for me, but a reverse subtraction or easy reciprocal division would also be convenient in some cases. I'm neutral on the idea, though.


Isiah Meadows me at isiahmeadows.com, www.isiahmeadows.com