6

So, I just read this blog post, and I was confused by the "ternary-operator is left-associative" part, so I ran the example code there-in in an interpreter:

$arg = 'T';
$vehicle = ( ( $arg == 'B' ) ? 'bus' :
             ( $arg == 'A' ) ? 'airplane' :
             ( $arg == 'T' ) ? 'train' :
             ( $arg == 'C' ) ? 'car' :
             ( $arg == 'H' ) ? 'horse' :
             'feet' );
echo $vehicle;

and indeed, it returns horse which is the counter-intuitiveness that was the point in the blog post.

Out of curiosity, I then tried to "make this work" by re-writing it to fit what I thought "left-associative" would want. I got this (formatting is weird, but it makes it clearer at least in MY head):

$arg = 'T';
$vehicle = ( ( $arg != 'B' ) ? 
                ( $arg != 'A' ) ? 
                    ( $arg != 'T' ) ? 
                        ( $arg != 'C' ) ? 
                            ( $arg != 'H' ) ? 
                                'feet' :
                            'horse' :
                        'car' :
                    'train' :
                'airplane' :
            'bus'
);
echo $vehicle;

Now, this works as expected in the sense that what ever character $arg is returns the vehicle that begins with that character (lower case of course, but that's not important here).

Curious still, because I'm not clear on WHY the former does not work, I wondered if the latter will fail in a right-associative language, because it does not seem like it would. So I tested it in a java interpreter. Code here for anyone who wants to try and save a few seconds.

class Main {
  public static void main(String[] args) {


    Character arg = 'a';
    String vehicle = ( ( arg == 'B' ) ? "bus" :
                        ( arg == 'A' ) ? "airplane" :
                        ( arg == 'T' ) ? "train" :
                        ( arg == 'C' ) ? "car" :
                        ( arg == 'H' ) ? "horse" :
                        "feet" );
    System.out.println(vehicle);
    vehicle = ( ( arg != 'B' ) ? 
                    ( arg != 'A' ) ? 
                        ( arg != 'T' ) ? 
                            ( arg != 'C' ) ? 
                                ( arg != 'H' ) ? 
                                "feet" :
                            "horse" :
                        "car" :
                    "train" :
                "airplane" :
            "bus"
    );
    System.out.println(vehicle);
  }
}

Both format works in java. So, what gives in php? I have heard that it is essentially evaluating the leading equalities as the final one ($arg == 'H'). But if so, that means its behaving as though it was

$vehicle = ((($arg == 'B') || ($arg == 'A') || ($arg == 'T') ||
             ($arg == 'C') || ($arg == 'H')) ? 'horse' : 'feet');

This does not make sense to me. In the second php example I gave, I only moved where the equalities go, nesting in the if true part of ternary expression, instead of the if false part. I don't see why the first method would not work if the second does. This seems more like an error rather than "this is how it should work", and I'm just misinterpreting things, which I must be.

NOTE: I know I can just wrap parenthesis around things to force the expression to be evaluated the way I want (which apparently is right-associative). I am not looking for "how to make this work", I want to know why it does not.

Miao Liu
  • 440
  • 4
  • 9

1 Answers1

3

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.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Oh, I think I see. Since `X` is not `0` or anything else that evaluates to `false`, the final evaluation is then Y. So in the first case, `$arg=='B'` is `false`...`$arg=='A'` is `false`...`$arg=='T'` is `true`...`train` evaluates to `true`...`car` evaluates to `true`...final value is `horse`? – Miao Liu Feb 19 '16 at 19:43
  • @MiaoLiu You got it. --- Conclusion: Left-associative ternary operator is a very bad design. Or to summarize the [linked blog](http://eev.ee/blog/2012/04/09/php-a-fractal-of-bad-design/): *PHP is bad design*. Stay away. – Andreas Feb 19 '16 at 20:12
  • So this means, under Java, the most deeply nested ternary is evaluated first, then the result of that expression is used as one of the return values of the outer expression. Then the outer expression will be evaluated and the return value of that is used for the next outer statement, etc. Correct? – Ungeheuer Oct 22 '17 at 19:20
  • @Ungeheuer Not entirely. Don't confuse [*precedence* and *evaluation order*](https://stackoverflow.com/a/6801431/5221149). – Andreas Oct 22 '17 at 19:25