Closed Zylann closed 4 years ago
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)
I am not against this, but "shorter" than ternary doesn't mean clearer. I am not familiar with python nor with this weird syntax, I would have paused a few seconds to digest it. I prefer ternary operator which can test any condition for being true or false, not just null
. Besides, if both a and b are not null, returning the first operand, say a, seems random to me.
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)
.
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.
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...
Php7 added "Null coalescing operator '??' "
<?= ($a ?? $b) ?>
will print b if a is null.
This is different then elvis operator.
Ouch, my bad... anyway, ?:
would work even in cases where a is null.
Would this be the same as the double pipe operator in JS?
var a = null
var b = 25
print(a||b)
Should print b, since a is null? If so, yea would love this!
Hmmm, I still don't see the real in-game use for this, since this would fail:
var id = dictionary.id || generate_new_id()
# Error: No key id in dictionary
@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()
A good example that I could use this for would be for export keywords.
Example:
export(int) var Chat_Limit
However, if Chat_Limit is not set, I would need to add:
if !Chat_Limit: Chat_Limit = 5
If or operator, now I do:
export(int) var Chat_Limit || 5
export(int) var Chat_Limit or 5 (||, or, or w/e)
This is kind of nitpicking as it only saves about a line of code, but damn it would be sexy.
@Dillybob92 Really bad example tho, as you can already do:
export(int) var Chat_Limit = 5
(sorry stupid misclick)
@bojidar-bg Oh true. Wow, didn't know that, so it defaults to 5 already? That's cool, thank you! That means Chat_Limit isn't set though?
@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...)
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
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.
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)
But you had to write a twice^^
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
.
What's wrong with C++'s ternary operator syntax?? It is great, easy and intuitive.
Is this still a desired behavior?
I'm still against this proposal and several other core contributors confirmed that sentiment, so I think it won't happen. Maybe with another syntax as proposed by @neikeq, but I don't see a lot of interest in the feature itself, so not sure it's worth introducing sugar at all.
So it's not going to happen? Not even as ??
? This is really useful. I don't see why not include it.
It's not that useful, it's just syntax sugar for the regular ternary operator.
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.
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()
That's disappointing, this can massively increase the readability of code in some cases.
For @stwupton last example, IMO it decreases readability. Trying to cram everything in a single line is not always helpful.
@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.
I still agree, but I'd maybe amend the issue title because using logic operators for this would feel a bit ambiguous.
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.
I still agree, but I'd maybe amend the issue title because using logic operators for this would feel a bit ambiguous.
If you think about it,
a else b
is probably one of the most natural syntax sugars fora if a else b
.
Fair enough, and that sounds like a pretty compelling reason for else
.
Now that I think of it, wouldn't else
be a bit ambiguous with inline if?
e.g.: What does a if a else b else c
do?
@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
?
E.g., is
"a" + 2 + 3
going to produce"a23"
or"a5"
? What abouta and b or c
?
Well the former, I hope it produces a runtime error :) The latter has clear precedence rules defined, so it's (a and b) or c
, but in the case of a if b else c else d
, the precedence rules are yet to be defined so that the result is predictable.
Here it's more than precedence: it's about what the operators actually are, based on the usage or not of parenthesis. If I replace the a else b
construct by null_coal(a, b)
and a if cond else b
by ternary(cond, a, b)
, the a if b else c else d
construct can be:
ternary(b, a, null_coal(c, d))
ternary(null_coal(b, c), a, d)
ternary(null_coal(b, null_coal(c, d)), a, )
# ^ invalid syntax for ternary() - or is `a if b` supported?
# If it is there are even more possible interpretations with a `test(cond, a)` operator.
Might not be that trivial to implement in the parser, and I'm not sure the added confusion is worth what the feature brings.
a if a else b else c
doesn't make much sense. If you were to interpret it as a if (a else b) else c
, it would imply the condition is not a bool. Which is possible, but a bool is much more common. The other else is solved by the operator precedence (though I don't know what it will be).
There's nothing wrong with a condition not being a bool.
Anyways, yeah it could either be a if (a else b) else c
or a if a else (b else c)
depending on precedence.
Either way it's not syntactically ambiguous.
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.
I don't have much to say on which precedence is better. But it can be done either way depending on what the language designers decide.
Anyways, since enums were merged with no controversy, I imagine that something like this which adds roughly the same utility to the language (I would argue more, since enums aren't any more readable than const IMO), should not have any issues with being merged if someone can provide a reasonable implementation?
@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 :smiley:.
I would be looking forward to this feature as well, with either the "else" or "??" syntax. Both sound good to me.
Feature and improvement proposals for the Godot Engine are now being discussed and reviewed in a dedicated Godot Improvement Proposals (GIP) (godotengine/godot-proposals) issue tracker. The GIP tracker has a detailed issue template designed so that proposals include all the relevant information to start a productive discussion and help the community assess the validity of the proposal for the engine.
The main (godotengine/godot) tracker is now solely dedicated to bug reports and Pull Requests, enabling contributors to have a better focus on bug fixing work. Therefore, we are now closing all older feature proposals on the main issue tracker.
If you are interested in this feature proposal, please open a new proposal on the GIP tracker following the given issue template (after checking that it doesn't exist already). Be sure to reference this closed issue if it includes any relevant discussion (which you are also encouraged to summarize in the new proposal). Thanks in advance!
In Python, Lua and Javascript (I think?), you can write this shortcut:
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: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?