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.


In reply to not 'another victim of precedence' ? 'It is true' : 'the code is false' by bobf

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post, it's "PerlMonks-approved HTML":



  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, details, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, summary, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.