NNBD, which is introducing many notations with ?, makes the ternary operator ?/: an eyesore more and more. For example, when null aware subscribing operation has shape of a?[b], then {a?[b]:c} is ambiguous at least mentally. The same thing goes for invocation {a?(b):c}.
In addition some proposals such as #357 might make the situation worse and worse.
T?, ??, ...?, and so on are OK, but ...
This issue is derived from #376 and #729.
This issue aims to alternate the ternaly operation a ? b : c.
This issue doesn't treat collection-if or if-statement, directly.
if(a) b else c // 1
if(a, b, c) // 2
if(a : b : c) // 3
and more
I'm very much in favour of doing something about the ternary operator.
Now, before NNBD syntax becomes sprinkled all over existing codebases, seems like an ideal time to let dartfmt do the heavy lifting of a syntax change. The ternary operator is still unique in its syntax and I think it should be possible to come up with a reasonably clean replacement.
That being said, using something based on if brings in lots of potential clashes with if statements, expressions and collection ifs. I think this is another corner of the language spec that is now proving to be kind of a dead end. It would be awesome to get if expressions. But collection-ifs will cause ambiguitiy. :(
I'm not sure the ambiguity is that much of a problem for collection-ifs.
It may make parsing harder because you have to parse-ahead for a while before being able to distinguish the expression from the collection element, but in case of a full-on ambiguity, where the same text can be either, it likely also doesn't matter which one it is.
Because a list/set collection element can be an expression, it wouldn't matter whether you do "produce (if test a else b)" or "if test (produce a) else (produce b)".
Example:
var x = [ if (test) 1 else 2 ];
Here it's ambiguous syntax, but the behaviors of the two options are indistinguishable.
We'd obviously have to go through all the syntax corners to make sure there are no cases I have missed.
Note: This issue is not for collection-if. Please discuss it at #729 or post a new issue.
This issue is not for collection-if
I second this. But I suggest we can go a step further: treat collection-ifs as part of DSL that lives only in list/map literals and has nothing to do with the rest of dart. By doing so, we achieve 2 goals: 1) we free ourselves from constant worries about consistency with collection syntax 2) we create a safe space for further experimentations with said DSL - whatever syntactic fantasy one can come up with will now find itself in a good company of collection-ifs, collection-fors, collection-await-fors, non-expressions looking like expressions, sets confused with entries etc. Looks like a win-win situation to me. WDYT? :-)
EDIT: we have yet another variant of "if", which has unique syntax not resembling anything else we have in dart: "conditional-import-if". I suggest that we have to simply treat it as another DSL and not worry about it.
The issue with collection-if here is that expressions occur in collections too, so there is some amount of syntactic overlap/ambiguity if expression-if starts using the same syntax. There is no need to change collection-if, and as stated this issue is not about doing so anyway, but changing expression-if interacts with collection-if, and that interaction needs to be discussed. (As stated above, I believe that interaction and ambiguity to be not-too-problematic).
It's true that removing ?/: syntax will free up some syntax that is otherwise causing us problems with syntactic ambiguity, and using a prefix if will definitely work instead.
The options are really:
<expression> ::= `if` `(` <expression> `)` <expression> `else` <expression>
or
<expression> ::= `if` `(` <expression> `)` <expression> (`else` <expression>)?
Anything else will just be inconsistent.
Since expressions must evaluate to a value or throw, omitting the else branch means that the omitted branch needs a default semantics.
The three obvious options are:
nullThe first one is what we currently do for ?/:. It also avoids ambiguity in if (t1) if (t2) e1 else e2 - which if does the else bind to? We solved it for statements, so we'd do the same thing for expressions, but it's a place where users can write code that doesn't do what they think.
The second one is adding null values, and it becomes ambiguous when an if occurs in a collection: Interpreted as an expression-if, it would add null to the collection, interpreted as a collection-if it adds nothing. So, not particularly useful.
Throwing means that it's effectively an error, and we should probably have gone with not allowing it anyway. Not practically useful.
So, my vote would be on requiring an else branch.
Ob-pedantry: The ?/: operator is not the ternary operator. It is the conditional (expression) operator which is a ternary operator (takes three operands).
The issue with collection-if here is that expressions occur in collections too, so there is some amount of syntactic overlap/ambiguity if expression-if starts using the same syntax.
I understand.
So, my vote would be on requiring an else branch.
I would also vote on requiring else branch. (The first title of this issue was "... mandatory else-part")
A syntax clean braking from if-statement and collection if is also attractive for me.
@lrhn: for the same reason as "else" being optional in collection-if, it can be made optional in
SomeWidget(
parameter: if (condition) value
);
as requested by this comment
I think this context is more similar to that of collection-ifs than to expression-if, so user's expectation would be on the side of it being a legal construct. WDYT?
@tatumizer null doesn't necessarily mean omitting argument, so your proposal require special syntactical treatment which could be called if-argument. If-argument which doesn't exist now make discussions more comprecated. I'd like to discuss deprecating ?/:.
I prefer if(a, b, c) as conditional expression, so far. It is a little confusing because it start with if(, but it's syntax is not ambiguous.
Is it feasible?
Using if (e1, e2, e3) is technically feasible. I personally think it's too far from the existing syntax to be preferable.
One design rule is that similar things should look similar, different things ahould look different.
This looks like a function call, but isn't. That's not unprecedented (assert is also not a function call), but it is one argument against. It would make the conditional expression easily delimited and easier to parse. It might confuse users coming from JavaScript, where you have the comma operator, and if (a, b, c) means if (c) after evaluating a and b for their side effects.
I can see the reasoning for "optional arguments". A non-else-condition in an optional argument position could mean not passing the argument, but currently it would only work for named optional parameters. Omitting a positional parameter would shift the remaining arguments up, which is not sound in general. If we had a way to omit optional arguments in the middle of a positional parameter chain, then it would fit better (not shift later arguments up, but still trigger default value in the function). That's a different feature.
Is comma operator in JavaScript or C populer for modern language like Dart?
Anyway, I would also like see clearer syntax for conditional expression than f(a, b, c).
What do you think about if(a : b : c)?
A non-else-condition in an optional argument position could mean not passing the argument, but currently it would only work for named optional parameters
Potential counter-argument: Named optional parameters are a major use case, so you are right, it will only work in 99% of cases :-)
Speaking of optional positional parameters: normally, we have either zero or one such parameter, for which skipping "else" doesn't cause any ambiguity. But even if someone defines two or more, "shifting" is not the idea that naturally comes to mind. We can say that for optional parameters of any kind, missing 'else' is equivalent to passing null or - even better - disallow omitting "else" in any situation when it can cause ambiguity.
The major problem with if-expression (and with the earlier collection-if) is: potential confusion about braces. In if-statement, braces denote a block, and in if-expression - either a set or a map. To make things worse, using braces in if-statement is a hallmark of a good style (from the style guide: "DO use curly braces for all flow control statements"). Many users have already acquired such a deep-rooted habit of decorating every "if" and "else" with braces that the very sight of braceless "if" would cause a panic attack in those individuals. Their instinct would be to immediately add braces to every if (cond) x else y resulting in sets being indiscriminantly generated everywhere throughout the program.
I think this paradox would be easy to resolve if the language required explicit disambiguation in the problematic case, e.g.
var x = if (cond) {...} else {...};
would be flagged. The fix is:
var x = if (cond) <int>{5} else <int>{3};
or even
var x = if (cond) <auto>{5} else <auto>{3};.
Should the restriction be enforced even in places where the compiler has enough information to recognize a set/map, as in
var x = if (cond) {5, 10} else {3, 7} is up for debate though. Maybe yes, maybe no.
@tatumizer
Set literal is one of the major reasons which makes the discussion #376 complicated. AFAIK, Set literal was newly introduced on 27th February by demand from the Flutter team, but the Flutter framework uses only type specified Set literals. So, deprecating Set literal without type argument might be feasible, but it is another breaking change, any way.
As I mentioned in the Motivation, non-affinity between NNBD and ? of current conditional expression is the cause of problems, but not Set literal, and it is the reason why I rename this issue to "deprecating ternary operator ?/:".
if(a : b : c) as conditional expression can be discussed without worry about the Set literal problem.
@lrhn
One design rule is that similar things should look similar, different things should look different.
Generally speaking, I totally agree with you.
That said, in context of replacing a ? b : c in existing code, syntactical clean break from if-statements and collection-ifs might be meaningful, while if(a : b : c) is resembles if-statements and collection-ifs slightly better than a ? b : c.
Just thought I'd give some thoughts from the perspective of an educator.
A ton of languages have the ternary operator with the same syntax as in dart; cond ? a : b. C/C++, JavaScript, Python, Ruby, Java, Crystal etc. Having a familiar syntax makes it much easier to read the intention of the code if you are coming from another language. While perhaps something like if(a: b: c) might be a bit cleaner, it also may be a bad idea to split away from the established convention.
There are a few other alternatives from other languages that might be worth exploring. MySQL uses the IF(cond, a, b); syntax. R has an ifelse function which can be invoked like so ifelse (cond, a, b). Smalltalk uses ifTrue and ifFalse, aka cond ifTrue:a ifFalse:b. And many other languages allow you to write something along the lines of var x = if cond a else b with some using := instead of a simple =. Some of these could be implemented by the user as a function/method/extension method while others would be a bit harder to implement without modifying the VM.
@tensor-programming
I agree with you, but this issue depends on #376.
If the only reason why the language team can't adopt a?[b] as null aware subscription is the ambiguity of {a?[b]:c} weather Map literal {(a?[b]): c} or Set literal {a ? ([b]) : c}, then I'd like to push this issue. The ambiguity is tolerable for me myself, so if the language team decide to adopt a?[b] as null aware subscription, then I'd like to close this issue regardless of other relatively minor problems.
I mean, my oder of precedence is ...
a?[b] as null aware subscripting and keep ternary operator a?b:c.a?[b] as null aware subscripting and deprecate ternary operator a?b:c.a?.[b] as null aware subscripting and keep ternary operator a?b:c.a?.[b] as null aware subscripting and deprecate ternary operator a?b:c.@Cat-sushi I do agree with you from the standpoint of #376. If the subscript syntax becomes at odds with the ternary then learning to use the subscript properly would pose a challenge for newer devs.
@Cat-sushi it worth noting that ternary operator wouldn't removed from the Dart language immediately as it will break a lot of code. So this issue wouldn't help to resolve the problem a?[b], otherwise Dart team risking to break Dart in the same manner as it was done with Pytnon 2 and 3.
Null-aware subscripting is now resolved.
@vanesyan Dart will introduce a migration tool for NNBD, and I'm proposing the migration tool converting ternary operations into new syntax.
The language team has decided that subscripting will have form of a?[b]. #376
Now, as I mentioned, I'm closing this issue, regardless of relatively minor problems remained.
Feel free to reopen this, or to post another issue to discuss deprecating conditional ternary operator.
Thank you.
Most helpful comment
The issue with collection-if here is that expressions occur in collections too, so there is some amount of syntactic overlap/ambiguity if expression-if starts using the same syntax. There is no need to change collection-if, and as stated this issue is not about doing so anyway, but changing expression-if interacts with collection-if, and that interaction needs to be discussed. (As stated above, I believe that interaction and ambiguity to be not-too-problematic).
It's true that removing
?/:syntax will free up some syntax that is otherwise causing us problems with syntactic ambiguity, and using a prefixifwill definitely work instead.The options are really:
or
Anything else will just be inconsistent.
Since expressions must evaluate to a value or throw, omitting the else branch means that the omitted branch needs a default semantics.
The three obvious options are:
nullThe first one is what we currently do for
?/:. It also avoids ambiguity inif (t1) if (t2) e1 else e2- whichifdoes theelsebind to? We solved it for statements, so we'd do the same thing for expressions, but it's a place where users can write code that doesn't do what they think.The second one is adding
nullvalues, and it becomes ambiguous when anifoccurs in a collection: Interpreted as an expression-if, it would addnullto the collection, interpreted as a collection-if it adds nothing. So, not particularly useful.Throwing means that it's effectively an error, and we should probably have gone with not allowing it anyway. Not practically useful.
So, my vote would be on requiring an
elsebranch.Ob-pedantry: The
?/:operator is not the ternary operator. It is the conditional (expression) operator which is a ternary operator (takes three operands).