Ternary operator left associativity

13.4k views Asked by At

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'?

5

There are 5 answers

5
200_success On BEST ANSWER

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';
3
Amadan On

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.

1
Jonathon On

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!

1
Dillshad Rana On

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"))))
?>
0
Ярослав Рахматуллин On

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.