vim icon indicating copy to clipboard operation
vim copied to clipboard

null coalescing operator assignment (foo ??= bar)

Open nevans opened this issue 3 years ago • 6 comments

First, thanks for adding the null coalescing operator! I know 8.2.1794 was a year and a half ago, but I haven't been keep closely up-to-date with new features and I only just discovered it today. 😄

It's not difficult to work around the absence of this feature. But it seems like an inconsistency or an oversight, since most other similar operators have an assignment version.

let foo = foo ? foo : 'bar'  " what I used to do
let foo = foo ?? 'bar'       " what I'm doing now
let foo ??= 'bar'            " what I'd like to do

Running that last line on v8.2.4842 gives the following message:

E15: Invalid expression: "??= "bar""

I personally find falsy-coalescing-assignment both easier to read and less likely to contain typos than those other more repetitive versions.

Thanks!


Edited to add:

The implementation of ??= should be subtly different from other operator= assignments, which convert a op= b into a = a op b. Instead, a ??= b should translate to something more like a ?? (a = b). E.g. see the ruby, javascript, or C# documentation.

nevans avatar May 02 '22 22:05 nevans

What other languages have a similar assignment operator?

brammool avatar May 02 '22 22:05 brammool

C# has it: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-coalescing-operator

nickspoons avatar May 02 '22 23:05 nickspoons

What other languages have a similar assignment operator?

OTTOMH, Dart, JavaScript, PowerShell and PHP.

dkearns avatar May 03 '22 09:05 dkearns

Yeah, I think it's a recent(-ish... since 2019?) addition to Javascript, C#, and PHP. One important distinction: their ?? and ??= operators evaluate the rhs only when the lhs is null or undefined.

IMO, the docs should be updated to reflect this difference. Perhaps something like the following?

- This is also known as the "null coalescing operator" but that's too
- complicated, thus we just call it the falsy operator.
+ This is similar to the "null coalescing operator" found in Javascript or C# or
+ PHP, except that it uses falsiness instead of null or undefined, thus we call it
+ the falsy operator.

Behaving less like a null coalescing operator and more similarly to vim's falsy-operator:

  • Python's a or b. But Python doesn't have any sort of or= assignment operator, as far as I know. 🙁
  • Perl's a || b. I'm not looking it up now, but I think Perl had ||= when I first tinkered with it, circa ~1996-7.
  • Ruby added ||= in 1999. (ruby is my primary language)

It's worth noting that ruby's notion of "falsy" is much more restrictive than vim's or javascript's: nil and false are falsy and everything else is truthy. Idiomatic ruby uses ||= ubiquitously for setting variable with defaults and for simple memoization of method results. The ||= operator isn't as useful when we need to distinguish between nil, false, and undefined, but most of the time it's very nice to use.

Before I knew about the ?? falsy operator, the top of my .vimrc contained:

if empty($XDG_CONFIG_HOME) | let $XDG_CONFIG_HOME=$HOME..'/.config'      | endif
if empty($XDG_DATA_HOME)   | let $XDG_DATA_HOME  =$HOME..'/.local/share' | endif

After I learned about ??, I updated it to:

let $XDG_CONFIG_HOME = $XDG_CONFIG_HOME ?? $HOME.."/.config"
let $XDG_DATA_HOME   = $XDG_DATA_HOME   ?? $HOME.."/.local/share"

And with ??= I'd be able to use:

let $XDG_CONFIG_HOME ??= $HOME.."/.config"
let $XDG_DATA_HOME   ??= $HOME.."/.local/share"

IMO that last one isn't just less typing; it's also easier to read/understand when quickly scanning through the code.. Like I said up top, it's not difficult to work around the absence of this feature. But it sure is nice to use when I have it.

Thanks for your consideration!

nevans avatar May 03 '22 17:05 nevans

looks golang had no such thing.. :-) // which by if/else maybe more explicit.. or if had to be, then maybe simply just ?= was better?

Shane-XB-Qian avatar May 09 '22 16:05 Shane-XB-Qian

I was just reminded of this ticket and wanted to share a detail that I left out in the description. The implementation of ??= should be subtly different from other operator= assignments, which convert a op= b into a = a op b. Instead, a ??= b should translate to something more like a ?? (a = b).

To quote from ruby's assignment syntax documentation:

There are also ||= and &&=. The former makes an assignment if the value was nil or false while the latter makes an assignment if the value was not nil or false.

Here is an example:

a ||= 0
a &&= 1

p a # prints 1

Note that these two operators behave more like a || a = 0 than a = a || 0.

And MDN's description of Javascript's nullish assignment operator makes the same point:

Logical nullish assignment short-circuits as well meaning that x ??= y is equivalent to:

x ?? (x = y);

And not equivalent to the following which would always perform an assignment:

x = x ?? y;

And C# specifies the same in its feature proposal, with more details. e.g:

  1. If A0 exists and B is implicitly convertible to A0, and B is not dynamic, then the type of a ??= b is A0. a ??= b is evaluated at runtime as:
  var tmp = a.GetValueOrDefault();
  if (!a.HasValue) { tmp = b; a = tmp; }
  tmp

Except that a is only evaluated once.

  1. Otherwise, the type of a ??= b is A. a ??= b is evaluated at runtime as a ?? (a = b), except that a is only evaluated once.

nevans avatar Jul 25 '22 15:07 nevans