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