Ternary operator left associativity
In any sane language, the ternary operator is right-associative, such that you would expect your code to be interpreted like this:
$a = 2;
echo ($a == 1 ? 'one' :
($a == 2 ? 'two' :
($a == 3 ? 'three' :
($a == 4 ? 'four' : 'other')))); # prints 'two'
However, the PHP ternary operator is weirdly left-associative, such that your code is actually equivalent to this:
<?php
$a = 2;
echo (((($a == 1 ? 'one' :
$a == 2) ? 'two' :
$a == 3) ? 'three' :
$a == 4) ? 'four' : 'other'); # prints 'four'
In case it still isn't clear, the evaluation goes like this:
echo ((((FALSE ? 'one' :
TRUE) ? 'two' :
$a == 3) ? 'three' :
$a == 4) ? 'four' : 'other');
echo ((( TRUE ? 'two' :
$a == 3) ? 'three' :
$a == 4) ? 'four' : 'other');
echo (( 'two' ? 'three' :
$a == 4) ? 'four' : 'other');
echo ( 'three' ? 'four' : 'other');
echo 'four';
Ternary Operator Associativity
The ternary operator is always right-associative
The ternary operator is right-associative, as we see in your first example (and as we see below, this is the only choice we have if we want to let the corresponding if-else
blocks to contain anything else than expressions that evaluate to booleans). Note that your second example leaves no room associativity, so this does it does not show an example of any left-associativity of the ternary operator.
/* example 1 */
a ? b : c ? d : e
==> { right-ass. } => a ? b : (c ? d : e), OK
==> { left-ass. } => (a ? b : c) ? d : e
/* |___|
\ not OK: since then both of these must be
booleans: that is restriction we don't have */
/* example 2 */
a ? b ? c : d : e
==> { only-valid-splitup } => a ? (b ? c : d) : e
(a ? b ? c) : d : e
/* |___|
\ not valid, there's no ? ? ternary operator */
a ? b ? (c : d : e)
/* |___|
\ not valid, there's no : : ternary operator */
Hence, the ternary operator's associativity is well-defined even if you nest ternary operator expressions. Take care, however, that doing so tends decrease code readability, and it is even outright recommended against this in the Language Guide - Basic Operators
...
Use the ternary conditional operator with care, however. Its
conciseness can lead to hard-to-read code if overused. Avoid
combining multiple instances of the ternary conditional operator into
one compound statement.
The ternary operator: not two unary operators, but a unique operator of its own
The ternary operator is a special operator not really directly related to any of the other operators in Swift; all other belong to the families of unary and binary operators.
Unary operators ...
Binary operators ...
Ternary operators operate on three targets. Like C, Swift has only one ternary operator, the ternary conditional operator (a ? b : c).
From the Language Guide - Basic Operators.
Since we're only allowed to customly define our own prefix
(unary) and infix
(binary) operators in Swift, I suspect you will have quite difficult time implementing your own true ternary operator, since you're limited to using it as two separate unary infix operators ?
and :
, which is naturally not the same as a single ternary operator. (You could always look at this somewhat old blog post by Nate Cook, explaining how to mimic a ternary operator by using two binary operators, however as currying is to be removed in Swift 3, I don't know if this will be possible for future Swift versions).
Ternary operator as both left operative and right operative.. or neither
Since the ternary conditional operator has its own place in the operator precedence table, (i.e. no other operator has exactly the same precedence as it), the associativity rule only applies when disambiguating a conditional operator from another.
Right to left associativity means that the implicit parentheses are around the rightmost ternary.
That is,
a ? b : c ? d : e
is equivalent to
a ? b : (c ? d : e)
.
https://en.wikipedia.org/wiki/Operator_associativity is a useful link.
Why is the conditional operator right associative?
If it evaluated from left to right, it'd look like this:
z = ((a == b ? a : b) ? c : d);
That is, it would use the result of the first conditional (a
or b
) as the boolean condition of the second conditional. That doesn't make much sense: that's like saying:
int z, tmp;
/* first conditional */
if(a == b) tmp = a;
else tmp = b;
/* second conditional */
if(tmp) z = c;
else z = d;
While perhaps one day you'll want to do exactly this, it's far more likely that each ?:
that follows is meant to add more conditions, like if
/ else if
/ else if
/ else
, which is what the right-associative binding yields:
int z;
/* first conditional */
if(a == b) z = a;
else /* second conditional */ if(b) z = c;
else z = d;
Nested Ternary-operator Associativity in php vs java
Left- vs right-associative is about precedence when stringing multiple operators together, e.g. A + B + C
.
For the ternary operator A ? B : C
, this only has meaning when the extra ternary operator replaces the C
part (or A
part):
A ? B : X ? Y : Z
(A ? B : X) ? Y : Z <-- left-associative (PHP)
A ? B : (X ? Y : Z) <-- right-associative (Java, C, C++, C#, Perl)
If the extra ternary operator is inserted in the middle (replacing B
), it can only have one meaning:
A ? X ? Y : Z : C
A ? (X ? Y : Z) : C
That is why PHP and Java agree on the second one. There is no left-/right-associative rule applied.
Ternary operator associativity in C# - can I rely on it?
Yes, you can rely on this (not only in C# but in all (that I know) other languages (except PHP … go figure) with a conditional operator) and your use-case is actually a pretty common practice although some people abhor it.
The relevant section in ECMA-334 (the C# standard) is 14.13 §3:
The conditional operator is right-associative, meaning that operations are grouped from right to left.
[Example: An expression of the forma ? b : c ? d : e
is evaluated asa ? b : (c ? d : e)
. end
example]
How does associativity of operator work during evaluation of expression?
Associativity tells you what happens if you have multiple instances of the same operator in a row. For example,
f() - g() - h()
parses as
(f() - g()) - h()
and not
f() - (g() - h())
because -
is left associative, not right associative.
None of this has anything to do with evaluation order, which determines which function is called first.
As for ?:
being right associative, it means
a ? b : c ? d : e
parses as
a ? b : (c ? d : e)
(This makes slightly more sense if you think of ?...:
as a single operator.)
However, ?:
guarantees left-to-right evaluation: The first operand is always evaluated first, then exactly one of the other operands (depending on the truth value of the first result).
In your example,
c=a>b? a=a*2: a=a+3
(please never put assignments inside ?:
like that in real code) is parsed as
c = ((a>b) ? (a=a*2) : (a=a+3))
This is entirely due to precedence, not associativity (we don't have multiple identical operators next to each other here).
a>b
is evaluated first (yielding false
), which causes a=a+3
to be evaluated (yielding 4
), which is then assigned to c
.
C++, conditional operator associativity
If ?:
was left associative, the statement
finalgrade = (grade > 90) ? "high pass" : (grade < 60) ? "fail" : "pass";
would be treated as
finalgrade = ((grade > 90) ? "high pass" : (grade < 60)) ? "fail" : "pass";
which (in this case) would not compile, since "high pass"
and (grade < 60)
have different types.
Since it is actually right associative, the statement is treated as
finalgrade = (grade > 90) ? "high pass" : ((grade < 60) ? "fail" : "pass");
Concatenated ternary operators with pyparsing
I think this operator is actually right-associative, not left. If I change your code to:
import pyparsing as pp
TERNARY_INFIX = pp.infixNotation(
pp.pyparsing_common.integer, [
(("?", ":"), 3, pp.opAssoc.RIGHT),
])
TERNARY_INFIX.runTests("""\
1?1:(0?1:0)
(1?1:0)?1:0
1?1:0?1:0
""", fullDump=False)
Then I get reasonable output, and no error for the input without parens:
1?1:(0?1:0)
[[1, '?', 1, ':', [0, '?', 1, ':', 0]]]
(1?1:0)?1:0
[[[1, '?', 1, ':', 0], '?', 1, ':', 0]]
1?1:0?1:0
[[1, '?', 1, ':', [0, '?', 1, ':', 0]]]
Here is a larger expression to evaluate the largest of 3 variables (from this C tutorial: http://cprogramming.language-tutorial.com/2012/01/biggest-of-3-numbers-using-ternary.html):
TERNARY = pp.infixNotation(
pp.Char("abc"), [
(pp.oneOf("> <"), 2, pp.opAssoc.LEFT),
(("?", ":"), 3, pp.opAssoc.RIGHT),
])
TERNARY.runTests("""\
(a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c)
a > b ? a > c ? a : c : b > c ? b : c
""", fullDump=False)
Gives:
(a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c)
[[['a', '>', 'b'], '?', [['a', '>', 'c'], '?', 'a', ':', 'c'], ':', [['b', '>', 'c'], '?', 'b', ':', 'c']]]
a > b ? a > c ? a : c : b > c ? b : c
[[['a', '>', 'b'], '?', [['a', '>', 'c'], '?', 'a', ':', 'c'], ':', [['b', '>', 'c'], '?', 'b', ':', 'c']]]
EDIT: I see now that this a similar situation to repeated binary operators, like "1 + 2 + 3". Left-associative, pyparsing parses them not as [['1' '+' '2'] '+' '3']
, but just ['1' '+' '2' '+' '3']
, and it is up to the evaulator to do the repetitive left-to-right evaluation.
When I added the ternary operator, I did not envision a chained form such as the one you are parsing. A one-line change to infixNotation
will parse your expression successfully with left-associativity, but like the chained binary operators gives an ungrouped result:
[1, '?', 1, ':', 0, '?', 1, ':', 0]
Like the repeated addition example, it is up to the evaluator to do the successive left-to-right evaluation, somcething like:
def eval_ternary(tokens):
operands = tokens[0]
ret = bool(operands[0])
i = 1
while i < len(operands):
ret = bool(operands[i+1]) if ret else bool(operands[i+3])
i += 4
return ret
If you want to hand-patch your pyparsing code, change:
elif arity == 3:
matchExpr = _FB(
lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr
) + Group(lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr)
to:
elif arity == 3:
matchExpr = _FB(
lastExpr + opExpr1 + lastExpr + opExpr2 + lastExpr
) + Group(lastExpr + OneOrMore(opExpr1 + lastExpr + opExpr2 + lastExpr))
^^^^^^^^^^
Make this change in pyparsing.py, or copy the definition of infxNotation
into your own code and change it there.
I'll make this change in the next release of pyparsing.
EDIT - Fixed in pyparsing 2.4.6, just released.
Related Topics
Converting <Br /> into a New Line for Use in a Text Area
Title Case a String Containing One or More Last Names While Handling Names with Apostrophes
Using PHP Substr() and Strip_Tags() While Retaining Formatting and Without Breaking HTML
Is There a PHP Function to Convert a Query String to an Array
PHP Curl: I Need a Simple Post Request and Retrival of Page Example
PHP Return Only Duplicated Entries from an Array
Disable Laravel's Built-In Error Handling Methods
How to Delete a Folder with Contents Using PHP
How Get Value for Unchecked Checkbox in Checkbox Elements When Form Posted
Curl , Get Redirect Url to a Variable
How to Remember Input Data in the Forms Even After Refresh Page
Get Orders Shipping Items Details in Woocommerce 3