[Prepended This is a failure story. In the spirit of the statement "You learn from your mistakes" I figured you might be able to learn from my mistake and avoid making it yourself. (and learn that B::Deparse is a great diagnostic tool).]

I wasted a lot of time this weekend when a seemingly correct grep() expression didn't work. A few modifications later and I had something that appeared to work correctly (but had a hidden bug). Eventually I asked for assistance and pfaut suggested that I look at perl's interpretation of my code by using B::Deparse on the script (perl -MO=Deparse filename.pl). This immediately revealed the problem with my original construction and also a serious bug in the expression that appeared to work. I've corrected the bug and I think its interesting enough to share with all of you.

@fks = grep { not $_->table_to->name eq $orig_fk->table_from->name and $col->eq([$orig_fk->columns_from], [$_->columns_to]) } @fks; # After B::Deparse @fks = grep { if (! $_->table_to->name eq $orig_fk->table_from->name) { $col_eq->([$orig_fk->columns_from], [$_->columns_to); } } @fks;

not( expr ) and expr: This details that the not() is associated only with the first expression and the second expression doesn't affect the overall result of the grep expression. The net effect was to always return false. This is what prompted me to look into the problem in the first place.

@fks = grep { my $r = not $_->table_to->name eq $orig_fk->table_from->name and $col->eq([$orig_fk->columns_from], [$_->columns_to]) $r; } @fks; # After B::Deparse @fks = grep { if (my $r = ! $_->table_to->name eq $orig_fk->table_from->name) { # Note that $r isn't being written to $col_eq->([$orig_fk->columns_from], [$_->columns_to); } $r; } @fks;

($r = not expr) and expr; $r: This details that while my expectation that $r would contain the result of the aggregate expression it really wasn't. It appeared to work because the first expression caught all the cases my data was presenting. The second expression still doesn't affect the overal result but I wouldn't know this until much later. This is the second and critical bug that B::Deparse highlighted for me.

@fks = grep not( $_->table_to->name eq $orig_fk->table_from->name and $col->eq([$orig_fk->columns_from], [$_->columns_to]) ), @fks; # OR @fks = grep not $_->table_to->name eq $orig_fk->table_from->name && $col->eq([$orig_fk->columns_from], [$_->columns_to]), @fks;

not( expr and expr ): This is how I should have written it. The first example requires parentheses after not() and wrapping both expressions to override the default precedence. The second is correct without parentheses but is uglier because it uses the && operator.

So... a long while later this is an object lesson in what happens when you neglect your precedence. I've gotten very used to the and and or operators' and not's higher precedence just threw me for a loop.

Anyone who wants to review perl's precedence should review perlop

Replies are listed 'Best First'.
A reply falls below the community's threshold of quality. You may see it by logging in.