I got bit by precedence today, and as it turned out the nature of my data complicated my debugging efforts. In the end nothing I discovered was all that surprising, but the road to get there seemed dark and twisty enough to merit a 'lesson learned' posting.
I had the following data:
%dest = (); %source = ( very_long_key_name => 1, another_key => 2 ); %names = ( 1 => 'name1', 2 => 'name2' );
I simply wanted to copy $source{very_long_key_name} to the %dest hash, doing a lookup in %names along the way. Specifically, I wanted to do this:
$dest{name} = $names{ $source{very_long_key_name} };
I ran the code and (thanks to warnings) got several 'Use of uninitialized value' warnings. A few strategically-placed print statements indicated that some of the data is missing. No problem - I'll just check for that before doing the lookup in %names.
$dest{name} = exists $source{very_long_key_name} ? $names{ $source{very_long_key_name} } : '';
My caffeine-starved brain thought the ''; looked lonely on a line all by itself, so I reversed the condition and moved it up. (In retrospect this was excessively silly - I said I was in need of caffeine.)
$dest{name} = not exists $source{very_long_key_name} ? '' : $names{ $source{very_long_key_name} };
But when I ran it, $dest{name} = 1 instead of 'name1':
print "name code = $source{very_long_key_name}\n"; # 1 print "converted = $names{ $source{very_long_key_name} }\n"; # name1 print "dest name = $dest{name}\n"; # 1
In reality, the data structures were much more complex and I thought I screwed up one of the dereferencing steps. I tested each one and everything seemed to be working fine in isolation. I could not figure out why the original value in $source{very_long_key_name} was getting copied rather than the lookup value in %names, until it dawned on me that the source of the '1' might not be what I thought it was.
Sure enough - a quick glance at perlop confirmed my suspicions: the conditional operator has higher precedence than 'not', so the code was being parsed like this (output from B::Deparse):
$dest{'name'} = (! (exists($source{'very_long_key_name'}) ? '' : $names{$source{'very_long_key_name'}} ) );
Since exists($source{'very_long_key_name'} evaluates to true, the right hand side simplifies to not ''. The empty string evaluates to false, so not '' is true, or 1. (FWIW, not 'scalar' = '' and not undef = 1.)
A set of parentheses did the trick:
$dest{name} = (not exists $source{very_long_key_name}) ? '' : $names{ $source{very_long_key_name} }; print $dest{name}; # name1
That's my 'duh' moment for the day. Nothing fancy, nothing complicated, and nothing that couldn't have been prevented by simply remembering precedence.
|
|---|