Many languages have a "null-coalescing" operator: a binary operator used to unwrap an optional/nullable value, or provide a "default" value if the LHS is null/none. It's usually spelled ?? (as in Javascript, Swift, C#, etc.).
I'm pondering the precedence of such an operator.
Why not just use no precedence? Parenthesis! S-expressions! Polish!
All interesting ideas! But this post will focus on a more "C-style" language perspective.
As for ??, it seems like there's a bit of variety. Let's start with a kind of basic operator precedence for a hypothetical C-style statically typed language with relatively few operators:
| prec | operators | types |
|---|---|---|
| 1 | Suffixes: a() | -> any type |
| 2 | High-prec arithmetic: a * b | integer, integer -> integer |
| 3 | Low-prec arithmetic: a + b | integer, integer -> integer |
| 4 | Comparisons: a == b | integer, integer -> boolean |
| 5 | Logic: a && b | boolean, boolean -> boolean |
There are subtly differences here and there, but this is just for comparisons. Here's how (some) different languages handle the precedence.
Below #5:
C#
PHP
Dart
Equal to #5
Javascript (Kinda;
??must be disambiguated from&&and||)Between #3 and #4:
Swift
Zig
Kotlin
So, largely 2 camps: very low precedence, or moderately low. From a brief look, I can't find too much information on the "why" of all of this. One thing I did see come up a lot is this: ?? is analogous to ||, especially if they both short-circuit. And in a lot of programming languages with a looser type system, they're the same thing. Python's or comes to mind. Not relevant to a very strict type system, but at least it makes sense why you would put the precedence down that. Score 1 for the "below/equal 5" folk.
However, given the divide, it's certainly not a straightforward problem. I've been looking around, and have found a few posts where people discuss problems with various systems.
https://forums.swift.org/t/nil-coalescing-operator-precedence/2954
https://www.codeproject.com/Tips/721145/Beware-The-null-coalescing-operator-is-low-in-the
These seem to center around this construct: let x = a() ?? 0 + b() ?? 0. Operator precedence is largely cultural/subjective. But if I were a code reviewer, attempting to analyze a programmer's intent, it seems pretty clear to me that the programmer of this wanted x to equal the sum of a() and b(), with default values in case either were null. However, no one parses ?? as having a higher precedence than +.
This example might be a bit contrived. To us, the alternate parse of let x = a() ?? (0 + b()) ?? 0 because... why would you add to 0? And how often are you chaining null coalescing operators? (Well, it can happen if you're using optionals, but it's still rare). But, it's a fairly reasonable piece of code. Those links even have some real-world examples like this people have fallen for.
Looking at this from a types perspective, I came to this conclusion; In a strongly-typed language, operator precedence isn't useful if operators can't "flow" from high to low precedence due to types.
To illustrate, consider the expression x + y ?? z. We don't know what the types of x, y, and z are. However, if ?? has a lower precedence than +, this expression can't be valid in a strictly typed language, where the LHS of ?? must be of an optional/nullable type.
If you look back at our hypothetical start table, you can see how operator types "flow" through precedence. Arithmetic produces integers, which can be used as arguments to comparisons. Comparisons produce booleans, which can be used as arguments to logical operators.
This is why I'd propose that it makes sense for ?? to have a precedence, in our example, between 1 and 2. That way, more "complex" types can "decay" though the precedence chain. Optionals are unwrapped to integers, which are manipulated by arithmetic, decayed to booleans by comparison, and further manipulated by logic.
Discussion questions:
What are some reasons for choosing the precedence of
??other than the ones discussed?Have any other languages done something different with the precedence, and why?
Has anyone put the precedence of
??above arithmetic?
Thanks!
Videos
Yup:
tb_myTextBox.Text = o.Member ?? "default";
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-coalescing-operator
Well, it's not quite the same as the conditional operator, but I think you're thinking of the null coalescing operator (??). (I guess you did say it was "similar" :) Note that "ternary" just refers to the number of operands the operator is - so while the conditional operator is a ternary operator, the null coalescing operator is a binary operator.
It broadly takes this form:
result = first ?? second;
Here second will only be evaluated if first is null. It doesn't have to be the target of an assignment - you could use it to evaluate a method argument, for example.
Note that the first operand has to be nullable - but the second doesn't. Although there are some specific details around conversions, in the simple case the type of the overall expression is the type of the second operand. Due to associativity, you can stack uses of the operator neatly too:
int? x = GetValueForX();
int? y = GetValueForY();
int z = GetValueForZ();
int result = x ?? y ?? z;
Note how x and y are nullable, but z and result aren't. Of course, z could be nullable, but then result would have to be nullable too.
Basically the operands will be evaluated in the order they appear in the code, with evaluation stopping when it finds a non-null value.
Oh, and although the above is shown in terms of value types, it works with reference types too (which are always nullable).
I just found this: The ?? operator aka the Null Coalescing Operator
You also have it in C/C++ as a GNU extension using the
?:operator :string pageTitle = getTitle() ?: "Default Title";
There isn't a way to do this by default in C++, but you could write one:
in C# the ?? operator is defined as
a ?? b === (a != null ? a : b)
So, the C++ method would look like
Coalesce(a, b) // put your own types in, or make a template
{
return a != null ? a : b;
}
which does the exact same thing?
No it doesn't. Because a bool? (or Nullable<bool>) and a bool are not the exact same thing.
An if condition must resolve to a bool. But a bool? might resolve to null at runtime. So the compiler won't allow that. You have to provide logic in the condition which will still return a bool in the event that the value in question is null. Probably the simplest way to do that is the null-coalescing operator and a default literal.
Assuming car.IsSold is nullable:
if (car.IsSold)
does not even compile since bool? does not implicitly cast to bool
The explicit cast
if ((bool)car.IsSold)
will throw an exception if car.IsSold is null
Using the null-coalescing operator
if (car.IsSold ?? false)
would work in this case, among other solutions.