Why Perl’s conditional operator is right associative

What happens if you change the associativity of the conditional operator? PHP implemented it incorrectly and now it’s part of the language. In What does this PHP print?, Ovid posted a bit of PHP code that gives him unexpected results. The code comes from a much longer rant by Alex Munroe titled PHP: a fractal of bad design:



The result is 'horse', and it will be for almost all values of $arg.

% php test.php
horse

I don’t care so much about the rant, but it told me the answer to this problem. The conditional operator is left associative in PHP, as documented in Operator Precedence. That almost made sense to me, and I know that putting parentheses around these things makes it more clear. I’m almost embarrassed to say that I couldn’t do it right off in this case. Where do I put them? With other operators it’s easy because the operator characters are next to each other. I started writing this to figure out the grouping when the operator characters are separated by other things.

Let’s simplify that a bit to we don’t have a big mess. Now there are only two:


The result is still 'horse' because we haven’t really changed anything:

% php simple.php
horse

Joel Berger gave a hint when he said that changing 'car' to '' yields 'feet':


And it does yield 'feet'::

% php null.php
feet

In Perl, the language I do know, the same operator is right associative (Why is the conditional operator right associative? on Stackoverflow explains why). Associativity, documented in perlop, comes into play when the compiler has to figure out which operator to do first when it has the same operator next to each other. In Learning Perl, we show this with the expontentiation operator since many other operators, such as multiplication and addition, don’t really care. The expontentiation is right associative because that’s what Larry decided it was (C doesn’t have this operator). That means it does the operation on the right before it does the operation on the left. You can see this when you use parentheses, the highest precedence operator, to denote the order you want and compare it to the version without the explicit grouping:

my $num = 4**3**2;    # 262144
my $num = 4**(3**2);  # 262144
my $num = (4**3)**2;  # 4096

We can do the same for the conditional operator in Perl. First, we translate the code to PHP, which is mostly changing == to eq:

# perl.pl
use v5.10;

my $arg = 'C';
my $vehicle = (
               ( $arg eq 'C' ) ? 'car' :
               ( $arg eq 'H' ) ? 'horse' : 'feet'
             );
say $vehicle;

This only outputs “car”:

% perl.pl
car

In Perl, we get the same behavior if we put parentheses around the second conditional:

# right.pl
use v5.10;

my $arg = 'C';
my $vehicle = (
               ( $arg eq 'C' ) ? 'car' :
               ( ( $arg eq 'H' ) ? 'horse' : 'feet' )
             );
say $vehicle;

We get the same result as perl.pl because we haven’t changed the order of anything:

% perl right.pl
car

To get the PHP behaviour, we have to change the parentheses like this, to surround everything up to the next ?. It took quite a mental leap for me to get this far because it’s so unnatural:

# left.pl
use v5.10;

my $arg = 'C';                                                        
my $vehicle = (
               ( ( $arg eq 'C' ) ? 'car' : ( $arg eq 'H' ) ) 
               	? 'horse' : 'feet'
             );
say $vehicle;

Now we get different behaviour:

% perl left.pl
horse

That’s really odd, but it’s also a small gotcha we mention in the Learning Perl class. You can have things such as ( $arg == 'H' ) as a branch. This use probably isn’t useful, but it’s a consequence of the syntax. We can do assignments, for instance:

my $result = $value ? ( $n = 5 ) : ( $m = 6 );

It’s easier to see this as a picture for the path through the conditionals. The right associative version branches either to an endpoint or another decision and there’s only one way to get to that endpoint.

Right associative, as in Perl

The left associative version has multiple ways to get to the same endpoint because either branch in the previous conditional can be the value for the next test. This also shows how 'car' isn’t the endpoint that you think it should be:

Left associative, as in PHP

Going back to do the same thing with the original chain of conditionals, we get this diagram that looks more like a corset lacing instruction than something we meant to program.

The full monty

However, we already know the answers in this particular case because some values are literals, so we can remove several paths. Now it’s much more clear that many paths are feeding into a path that must end up at 'horse'.

The full monty

In fact, the only way to get to 'feet' is to be any letter that is not B, A, T, C, or H. Joel figured this out by changing 'car' to the empty string, which has this diagram:

Joel’s change

The only way to get to 'horse' is to be exactly H. The other letters must end up at 'feet' because they all end up at the empty string. Every other string ends up at 'feet' because they are not exactly H.

Maybe the complicated stuff makes sense to PHP programmers. I don’t know. It’s more likely that they don’t do these sorts of things, at least if they’ve read the advice in the PHP manual. Some people blame Perl since PHP inherited from Perl, but it seems like a yacc error that they can’t fix for backward compatibility. It’s not like that’s never happened to Perl

Leave a comment

9 Comments.

  1. You sure do make me sound smart ๐Ÿ™‚ Brilliant write-up. I really like the flow diagrams.

  2. I don’t feel brilliant after struggling with this for an hour. And, not that they took me long to make, but if anyone wants them, I’ve uploaded the graffle files.

  3. All those diagrams, but in the end you didnโ€™t show what needs to be done to make it work! So here is the version that works:

    < ?php // test.php
      $arg = 'T';
      $vehicle = ( ( $arg == 'B' ) ? 'bus' :
                 ( ( $arg == 'A' ) ? 'airplane' :
                 ( ( $arg == 'T' ) ? 'train' :
                 ( ( $arg == 'C' ) ? 'car' :
                 ( ( $arg == 'H' ) ? 'horse' :
                   'feet' ) ) ) ) );
      echo $vehicle;
    ?>

    Though it probably helps to indent it differently:

    < ?php // test.php
      $arg = 'T';
      $vehicle = (
        ( $arg == 'B' ) ? 'bus' : (
          ( $arg == 'A' ) ? 'airplane' : (
            ( $arg == 'T' ) ? 'train' : (
              ( $arg == 'C' ) ? 'car' : (
                ( $arg == 'H' ) ? 'horse' : 'feet'
              )
            )
          )
        )
      );
      echo $vehicle;
    ?>

    โ€œPrettyโ€ is something else, though.

  4. Kelley Huston

    This is actually very similar to how MS Excel handles IF/ELSE statements. Having done quite a bit within Excel I did recognize it….wonder if that’s where they got the “idea”?

    =IF(A1="B","bus",IF(A1="A","airplane",IF(A1="T","train",IF(A1="C","car",IF(A1="H","horse","feet")))))
    

    Not trying to say this is “right”, because I think the Perl way makes more sense too, but essentially PHP is doing the “nested if” thing….

  5. Thank you for this helpful post, as always.

    Could I ask you a question about your last paragraph? Is there any relation between yacc and left-associativity of ?: of PHP?

    (It would be very surprising to me if that associativity is just a result of yacc’s bug.)

  6. Actually tradition does and should play the largest role. Suppose, for example, you decided to make addition right associate, instead of left? While convenient in some cases, it would confuse those familiar with addition.

    The ternary (x ? y : z) operator was right associative in C, and in 1987 a lot of folks were used to that, including Larry and me. Violating that convention would have made the ternary operator difficult to use for those already familiar with it.

    In math, exponentiation was right-associative (top-down associative using standard notation) in mathematics long before Perl came along, and Larry (happily) followed tradition.

    Marpa has the ability to handle all these associativity variants efficiently. I want to show off Marpa’s capabilities, but screwing around with non-traditional associativity (I’ve learned the hard way) is counter-productive. My readers lose their moorings in past practice and wonder what I’m talking about, and any point I’m trying to make gets lost. Language designers take note.

Leave a Reply

Your email address will not be published. Required fields are marked *