Ternary operator left associativity [duplicate]
Asked Answered
T

5

47

In the PHP manual, I find the following 'user contributed note' under "Operators".

Note that in php the ternary operator ?: has a left associativity unlike in C and C++ where it has right associativity.

You cannot write code like this (as you may have accustomed to in C/C++):

<?php 
$a = 2; 
echo ( 
    $a == 1 ? 'one' : 
    $a == 2 ? 'two' : 
    $a == 3 ? 'three' : 
    $a == 4 ? 'four' : 'other'); 
echo "\n"; 
// prints 'four' 

I actually try it and it really prints four. However I could not understand the reason behind it and still feel it should print two or other.

Can someone please explain what is happening here and why it is printing 'four'?

Triturable answered 13/12, 2013 at 4:33 Comment(5)
Add the parentheses implied by the associativity, and you will see why.Maffei
Visual representation of the issue: i.imgur.com/1zgFd.jpgVelamen
I mapped out the flow of left associativity in Why Perl’s conditional operator is right associativeBernal
PHP uses left-associativity by default. In reality, left-associativity is almost never what you actually want, so PHP's default doesn't really make much sense. In fact, this is being deprecated in PHP 7.4.Ether
I don't consider strangers on SO to be friends, so let's just establish this right here and now. Strangers don't even let strangers use PHP. Really, just stay away.Pforzheim
D
69

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';
Dario answered 6/7, 2016 at 18:24 Comment(5)
Am I reading it correctly that strings are being coerced into booleans? (If so, wow.)Christcrossrow
@Christcrossrow no, the ternary operator here is wrapped in echo which will print to the console. it will print one of the strings, depending on how $a evaluates in the equations. I still don't get "left" associativity though!Desilva
@DianaHolland, but the portion before the ? is a string.Christcrossrow
@Wildcard: you are reading it correctly this is business as usual in the PHP world. nothing shocking : )Psychosis
You really make me understand the concept of left-right associative, big thanks.Batfish
H
26

Because your whole expression evaluates as if it was (......) ? 'four' : 'other'. Since the first element is probably something truthy, it gives you 'four'. In saner languages, where ?: has right associativity, the whole expression evaluates as if it was $a == 1 ? 'one' : (......), where if $a is not 1, you go on to test other things.

Halicarnassus answered 13/12, 2013 at 4:39 Comment(3)
Of course, you should be parenthesising your complex expressions anyway, so in my opinion the point is moot.Khoury
@NiettheDarkAbsol: In my opinion, you should be parenthesising for readability. The code in OP is perfectly legible, in my opinion, in a sane (non-PHP) language, and it actually gets less so if you use parentheses.Halicarnassus
Nested ternaries are too confusing so I never user them, with or without parentheses. But the answer is great.Beefcake
K
7

This is what I came up with to help myself understand left vs. right associativity for the ternary operator.

// PHP

$a = "T";
$vehicle =  $a == "B" ? "bus" :
            $a == "A" ? "airplane" :
            $a == "T" ? "train" :
            $a == "C" ? "car" :
            $a == "H" ? "horse" : "feet";

            // (as seen by the PHP interpreter)
            // INITIAL EXPRESSION: ((((($a == "B" ? "bus" : $a == "A") ? "airplane" : $a == "T") ? "train" : $a == "C") ? "car" : $a == "H") ? "horse" : "feet");
            // STEP 1:             (((((FALSE ? "bus" : FALSE) ? "airplane" : TRUE) ? "train" : FALSE) ? "car" : FALSE) ? "horse" : "feet")
            // STEP 2:             ((((FALSE ? "airplane" : TRUE) ? "train" : FALSE) ? "car" : FALSE) ? "horse" : "feet")
            // STEP 3:             (((TRUE ? "train" : FALSE) ? "car" : FALSE) ? "horse" : "feet")
            // STEP 4:             (("train" ? "car" : FALSE) ? "horse" : "feet")
            // STEP 5:             ("car" ? "horse" : "feet")
            // FINAL EVALUATION:   ("horse")

            // If you used the initial expression here (with the parenthesis) in a different language, it would also evaluate to "horse."

echo $vehicle; // gives us "horse"

This is as opposed to:

// EVERY OTHER LANGUAGE

var a = "T";
var vehicle =   a == "B" ? "bus" :
                a == "A" ? "airplane" :
                a == "T" ? "train" :
                a == "C" ? "car" :
                a == "H" ? "horse" : "feet";

                // (as seen by the other language's interpreter)
                // INITIAL EXPRESSION: (a == "B" ? "bus" : (a == "A" ? "airplane" : (a == "T" ? "train" : (a == "C" ? "car" : (a == "H" ? "horse" : "feet")))));
                // STEP 1:             (FALSE ? "bus" : (FALSE ? "airplane" : (TRUE ? "train" : (FALSE ? "car" : (FALSE ? "horse" : "feet")))))
                // STEP 2:             (FALSE ? "bus" : (FALSE ? "airplane" : (TRUE ? "train" : (FALSE ? "car" : "feet"))))
                // STEP 3:             (FALSE ? "bus" : (FALSE ? "airplane" : (TRUE ? "train" : "feet")))
                // STEP 4:             (FALSE ? "bus" : (FALSE ? "airplane" : "train"))
                // STEP 5:             (FALSE ? "bus" : "train")
                // FINAL EVALUATION:   ("train")

                // If you used the initial expression here (with the parenthesis) in PHP, it would also evaluate to "train."

console.log(vehicle); // gives us "train"

If you notice, in the PHP example, the innermost expression is on the left, and on the second example, the innermost expression is on the right. Each step evaluates the next innermost expression until there is a single result. Parenthesis are clearly very important if you're going to nest ternary operations in PHP!

Kathrynekathy answered 12/1, 2018 at 21:29 Comment(1)
I don't think your representation of "every other language" is correct. From my understanding, most questions lazily evaluate the last 2 subexpressions of the conditional operator, so a lot of these expressions would never be evaluated.Haphazard
G
0

I could not wrap my head around the example from:

https://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/

So I came here and I still could not wrap my head around it, so I had to step through it.

@amadan has the best answer, imo.

This prints horse, not train.

// 0
$arg = 'T';
$vehicle = 
    $arg == 'B' ? 'bus' :
    $arg == 'A' ? 'airplane' :
    $arg == 'T' ? 'train' :
    $arg == 'C' ? 'car' :
    $arg == 'H' ? 'horse' :
    'feet' ;
// 1
$vehicle = 
>   FALSE       ? 'bus' :
    $arg == 'A' ? 'airplane' :
    $arg == 'T' ? 'train' :
    $arg == 'C' ? 'car' :
    $arg == 'H' ? 'horse' :
    'feet' ;

// 2
$vehicle = 
    FALSE       ? 'bus' :
>   FALSE       ? 'airplane' :
    $arg == 'T' ? 'train' :
    $arg == 'C' ? 'car' :
    $arg == 'H' ? 'horse' :
    'feet' ;

// 3
$vehicle = 
>   (FALSE? 'bus' : FALSE? 'airplane' : TRUE)? 'train' :
    $arg == 'C' ? 'car' :
    $arg == 'H' ? 'horse' :
    'feet' ;

// 4
$vehicle = 
>   true ? 'train' :
    $arg == 'C' ? 'car' :
    $arg == 'H' ? 'horse' :
    'feet' ;

// 5 
$vehicle = 
>   ('train' : $arg == 'C') ? 'car' :
    $arg == 'H' ? 'horse' :
    'feet' ;

// 6 
$vehicle = 
>   (true ? 'car' : $arg == 'H') ? 'horse' :
    'feet' ;

// 7 
$vehicle = 
>   (true) ? 'horse' : 'feet' ;

You can see what left associative means in step 5, if I understand correctly.

Gulosity answered 13/8, 2017 at 18:10 Comment(0)
T
-1

If you add parentheses, the problem will be solved. Look at the following example:
Without the parentheses, the grade is always D when the marks are greater than 50 but it works fine for marks <= 49.
In order to make the program work as it should, I have added parentheses. It is very easy to know how many parentheses to enter if it is typed like this.

<?php
 $marks_obtained = 65;
 $grade = null;
 //Use parentheses () otherwise the final grade shown will be wrong.
//Excluding the first line, for each additional line, 
//we add a parenthesis at the beginning of each line and a parenthesis at the end of the statement.
echo $grade = $marks_obtained >= 90 && $marks_obtained <= 100 ?  "A+"
              : ($marks_obtained <= 89 && $marks_obtained >= 80 ? "A"
              : ($marks_obtained <= 79 && $marks_obtained >= 70 ? "B"
              : ($marks_obtained <= 69 && $marks_obtained >= 60 ? "C"
              : ($marks_obtained <= 59 && $marks_obtained >= 50 ? "D"
              :                                                     "F"))))
?>
Tracery answered 16/10, 2014 at 18:57 Comment(1)
except needing to add parentheses because the language is broken kind of misses the point...Tamberg

© 2022 - 2024 — McMap. All rights reserved.