I want to stress that I am not pushing for any change. I just want to (re-)open the discussion on it and also share my design insight about a block indenting design versus Rust's brace-delimited blocks.
Some prior discussion pointed to the advantage of the conciseness, less noise, and equivalent juxtaposition contrast in the absence of syntax highlighting of the ternary operator:
!prev ? cur : prev + cur
versus:
if !prev { cur } else { cur + prev }
Please read that prior discussion and the linked issue-tracker justifications for the context of my proposal below.
Note another advantage of the ternary operator over Rust's brace-delimited if-else
is that Rust will allow the absence of parenthesis:
let x = if !prev { cur } else { cur + prev } + 1
Whereas, the ternary operator's precedence and lack of bracing on the tail end, requires parenthesis:
let x = (!prev ? cur : cur + prev) + 1
However, the flipside is that for programmers who don't memorize the ternary precedence and implicit right-to-left grouping, visual ambiguities can result in programmer error:
let x = 1 + !prev ? cur : cur + prev + 1
So I contemplated that assuming syntax highlighting is always present, then the optimum for Rust (and even for a language that employs block indenting instead of brace-delimited blocks) would be to drop the braces and employ parenthesis for the single-line ternary case:
if (!prev) cur else cur + prev
Unfortunately, without syntax highlighting the contrast of else
is lost in a thicket of words:
if (!prev) cur else cur + prev
I don't think we should dismiss the case of code being read in a "view source" or text editor scenario where syntax highlighting is not present. Designing a language to be independent of tools is an important goal as well.
One possible solution is to require the keywords to be uppercase (at least in this single-line scenario but consistency of case of keywords is important), but I think it to be noisy:
IF (!prev) cur ELSE cur + prev
Especially in the non-single-line case (shown below with block indenting instead of braces):
IF !prev
cur
ELSE
cur + prev
And redundant when we have syntax highlighting present:
IF (!prev) cur ELSE cur + prev
An alternative for the single-line case, is to require :
instead of else
:
if (!prev) cur : cur + prev
But if we are going to butcher if-else
for the single-line case, why not just require the ternary operator in that case:
!prev ? cur : cur + prev
The arguments against ternary are:
- Strong reason needed to revert the prior decision to remove it.
- Consistency.
- Explicit semantics of
if-else
versus symbol soup. - Parser grammar conflicts or just visual confusion with other uses of
:
in expressions, e.g. coercion to a typex : Type
. - Nested ternary is difficult to read especially without nested parenthesis.
- Visual (not grammar) ambiguities due to precedence and right-to-left grouping.
My responses:
-
For Rust since it has the brace-delimiting for contrast, the only reason is to make it less noisy and more concise. This may not be a strong enough justification for Rust.
-
Consistency is important, and for Rust the brace-delimited syntax works in both single-line and multi-line cases, albeit with slightly more noise, verbosity, and wasted vertical screen real estate:
if !prev { cur } else { cur + prev }
if !prev { cur } else { cur + prev }
But the block indenting language case:
if !prev cur else cur + prev
Can't maintain contrast in the single-line case without syntax highlighting:
if (!prev) cur else cur + prev
if !prev : cur else cur + prev
-
Rust's brace-delimiting maintains the explicitness of
if-else
without losing contrast in the absence of syntax highlighting albeit with more noise and verbosity of braces. A block indenting language has an less noisy and concision advantage in the multi-line case, but can't maintain consistency in single-line case unless force more noise in the multi-line case:if !prev : cur else cur + prev
Or:
if (!prev) cur else cur + prev
With the latter being superior in the absence of syntax highlighting:
if (prev)
As compared to:
if prev
But in the single-line case this loses contrast around
else
in the absence of syntax highlighting:if (!prev) cur else cur + prev
Albeit just replacing the
else
with:
is more consistent than a ternary:if (!prev) cur : cur + prev
-
Assuming there are no conflicts in the grammar, it is possible for the compiler to generate an error to force the programmer to nest such visually confusing cases in parenthesis. Note my proposal to require type names to begin with uppercase and variable names to not, IMO resolves the visual ambiguity for the case of coercion
prev ? cur : Type : prev
versusprev ? cur : prev
without parenthesis although I'd prefer to see that writtenprev ? cur:Type : prev
. Even in a language (e.g. Scala) which enables constructor without prepending withnew
, afaics there isn't ambiguity in the grammar nor visually betweenprev ? cur:Type : prev
andprev ? cur : Type(prev)
. -
It is possible for the compiler to disallow nested ternary operators which aren't grouped by parenthesis.
-
When the expression to the left of ternary's first operand expression includes an operator which could be visually ambiguous due to the absence of parenthetical grouping, the compiler can require that either the first operand of the ternary or the entire ternary be enclosed in parenthesis:
let x = (1 + !prev) ? cur : cur + prev + 1
Or:
let x = 1 + (!prev ? cur : cur + prev + 1)
Or in a block indenting language, instead only replace the
else
with a colon:let x = if (1 + !prev) cur : cur + prev + 1
Or:
let x = 1 + if (!prev) cur : cur + prev + 1
Thus I have concluded that for Rust, the best decision is no change, i.e. no ternary operator. For a block indenting language, it appears the optimal design is if-:
for single-line if-else
only when it is contained in an expression that is assigned:
let x = if (!prev) cur : cur + prev
foo.method(if (!prev) cur : cur + prev)
And note if
expression is also an assignment (aka operand) to an implicit if
function:
if (if (!prev) cur : cur + prev) x = cur
So instead of where the if
expression is not assigned, thus is only a statement:
if (!prev) x = cur : y = cur + prev
Programmer must write:
if (!prev) x = cur
else y = cur
Or:
if (!prev)
x = cur
else
y = cur