godot: GDScript idea: add a null-coalescing operator, which returns first non-null value

In Python, Lua and Javascript (I think?), you can write this shortcut:

    a = None
    b = "Hello"
    print(a or b)

prints “Hello”, and still evaluates to True if used in an if. Booleans behaviour is unchanged.

In GDScript, print(str(a or b)) would be readable and shorter to write than:

    var result = a
    if a == null:
        result = b
    print(str(result))

And even shorter than ternary, and also allows to write and evaluate a only once instead of two. it could be chained as well: a or b or c or d would fall back to the first one that is not null. What do you think?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 17
  • Comments: 45 (27 by maintainers)

Most upvoted comments

Let me re-propose using the else operator from ternaries instead of the or operator. The rationale is that it has not function on its own currently, except to spam errors.

The examples from the last comments would look like this:

    value = value else fallback1 else fallback2
    ## do something with value

func example(thing=null):
    thing = thing else ThingType.new()
    ## do something with thing

If you think about it, a else b is probably one of the most natural syntax sugars for a if a else b.

I am going to go forth and reopen this issue, just because it has attracted a lot of comments. This does not mean it would be implemented though, since it is here for discussion.

What do you think?

I find it very confusing. you can simply do:

print(a if a else b)

or, to be more explicit (and include the empty string):

print(a if a != null else b)

What’s wrong with C++'s ternary operator syntax?? It is great, easy and intuitive.

But you had to write a twice^^

I would expect that

 print(a or b)

prints “Hello” if either a or b evaluates to true

I think it would make more sense to write

print(a else b)

It’s not that useful, it’s just syntax sugar for the regular ternary operator.

I’m against using the or keyword for this. a or b is a boolean operation, it’s already taken. I propose using ?? like C# does. e.g.: a ?? b.

@vnen On the other hand, it is very helpful to avoid conditional branching logic when all you really want to do is express the value of something that could be null.

func example(value):
    if not value:
        value = fallback1
    if not value:
        value = fallback2
    ## do something with value

versus

func example(value):
    value = value if value else (fallback1 if fallback1 else fallback2)
    ## do something with value

versus

func example(value):
    value = value or fallback1 or fallback2
    ## do something with value

This is not about trying to get instructions on a single line. This is about 1. avoiding having to use conditional branching control flow when all you really want to do is express the value of something, and 2. nesting ternaries is terrible.

This example, expressing fallback values for a variable, is pretty much its primary use case. However, this comes in handy surprisingly often when working with a language that allows null values.

It is also useful when you want a function to have default values for an argument that need to be initialized.

func example(thing=null):
    thing = thing or ThingType.new()
    ## do something with thing

That said, the ternary expression can also be used for this, though it is ugly:

func example(thing=null):
    thing = thing if thing else ThingType.new()
    ## do something with thing

It comes down to the fact that the ternary expression is not a good fit for these cases because we don’t actually want a 3-ary operation. We just want to short circuit null values.

No, it’s not. If you did, say, a() if a() != null else b(), it would run a() twice (if it’s not null), because it doesn’t remember the value, making for an unnecessary and maybe big slow operation. You can replicate the null coalescing operator with

var tmp = a()
var val = tmp if tmp != null else b()

, but a or b/a ?? b is way shorter, which is the point.

Besides, if both a and b are not null, returning the first operand, say a, seems random to me.

My thoughts precisely. I was also not aware that Python and Lua support such a syntax, but at first sight it seems highly confusing to me. What would be the output of print("Hello" or "world")? Or print(null and "Hello")?

IMO it makes more sense to stay consistent and treat a or b and a and b as conditionals in all situations, such they should always resolve to True or False.

What you suggest in the OP can be achieved with print(a if a else b), which is IMO clearer than print(a or b).

I would be looking forward to this feature as well, with either the “else” or “??” syntax. Both sound good to me.

@mwerezak I wouldn’t advise for making new features to the language right now, because they would have to either wait or block the typed gdscript PR (probably). I might help you do it (or do it outright, if you don’t want) after it is merged 😃.

I guess I didn’t say enough. If the precedence was a if (a else b) else c, wouldn’t it be harder to parse? It’d break existing code: a if a else b (normally done) would be interpreted as a if (a else b), which is invalid, unless the parser can backtrack a bit. I said a bool is more common, as one reason the precedence should bind the first else to the if, whether the parser is smart or not.

@neikeq you are not supposed to use the two together without some parenthesis.

There are many operators which are order-dependent or hard to reason about without parentheses. E.g., is "a" + 2 + 3 going to produce "a23" or "a5"? What about a and b or c?

I have very recently written some code wishing that I had something like Dart’s null-aware operators.

So for example, an excerpt from the code I wish I had null-aware operators for…

Without Null-aware Operators

var last_path = _move_to_paths.back() if _move_to_paths.size() != 0 else Vector3()
_move_total_magnitude += (path - last_path).length()

With Null-aware Operators

_move_total_magnitude += (path - (_move_to_paths.back() ?? Vector3())).length()

Ok, let’s get started counting if there are many people who want it. As @akien-mga said:

With our fingers. If it doesn’t fit on one hand, it’s probably many.

So far I count 3 ± 1 people, still not enough for a hand.

Now with ternary-if we can do something like this: a = something if something else 3 and it looks kinda funny. Some syntax sugar could be nice. expression else value proposed by @bojidar-bg seems to nice be idea: a = something else 3

@Dillybob92 Well, it would default to 5 in case it isn’t set in the scene (you can unset it via the “refresh” button next to the value).

We might make the expression else value operator evaluate the expression as normal, but, instead of failing on errors, make it take on the value part, WDYT? (instant try-catch, but, who cares…)

(sorry stupid misclick)

@bojidar-bg in your example you would need a “tryget”, not a “get”. But GDScript Dictionaries don’t have such a thing as far as I know.

tryget is not the same topic but also has the advantage of fetching the item only once in case of a read and be able to use it in a one-liner (so instead of if d[key]: d[key] you can write d.tryget["key"] which returns null if not found).

I see you wrote a helper to generate the entry, so you could write this if you really want to:

var id = tryget(dictionary, "id") || generate_new_id()

But this is a bad specific example, I would just have a helper doing this:

var id = get_or_create_id()

I think that @ajacobsen’s binary-ternary-else idea is pretty good actually.

Php has it as the “elvis operator”:

<?= escape($_GET['title'] ?: 'No title in GET') ?>

In GDScript it would look like:

var x = selection else defaultSelection

Finally, in JavaScript this is used and overused all the time, so it might be either somewhat good or terribly bad…

print("Hello" or "world") would print “Hello”, and print(null and "Hello") would print null, just as in Python and Lua. The logic behind is that the operators don’t return True or False, but the last evaluated argument (that was evaluating as True or False anyways). However if you use comparison operators in the expression (<>=!=not) then you’ll get booleans as usual.

I’ve mostly used this in Lua, where ternary doesn’t exist http://lua-users.org/wiki/TernaryOperator. I was just doing the suggestion because I’ve seen this technique used not only in Lua, but also in Javascript, and the fact Python supports it could mean it could have been requested and used for a reason.

I’m ok with explicit ternaries, just wanted to know the community opinion.

Edit: another point is that the first operand is evaluated only once.