Reputation: 5716
I have a Perl program that calls a function, in this case ref
, and checks the result. Specifically, I am testing that a variable is a hash reference. In that case, ref
will return 'HASH'
. I tested it and it worked.
Then I decided to log it, adding a print
that displays the result of the same call, but it didn't work correctly. Here is a reduced version:
use strict;
use warnings;
my $book_ref = {};
$book_ref->{'title'} = 'The Lord of the Rings';
if (ref $book_ref eq 'HASH') {
print "ref \$book_ref is a " . ref $book_ref . "\n";
}
print "Program is over\n";
To my surprise, this was the output:
ref $book_ref is a Program is over
And despite using strict
and warnings
there were neither errors nor warnings.
The call to ref
is exactly the same (it's a copy and paste), but while it works correctly inside the if
condition, print
doesn't display anything, and actually seems to be interrupted, as the newline character is clearly skipped. Why does the behaviour change?
Upvotes: 1
Views: 164
Reputation: 5716
The reason is that the function ref
is called without parentheses, and this causes the line to be parsed incorrectly.
ref
is called inside the if
, the condition is clearly delimited by parentheses, which means that ref
knows perfectly well what its argument is: $book_ref
. There's no ambiguity.Instead, when printing the result, the lack of parentheses means that Perl will parse the line in a way that was not intended:
$book_ref
and "\n"
. In scalar context, $book_ref
evaluates to a string like HASH(0x1cbef70)
, therefore the result is the string "HASH(0x1cbef70)\n"
ref
will be called on the string "HASH(0x1cbef70)\n"
, producing as output an empty string: ''
.print
prints the empty string, that is, nothing, and stops there. The newline character \n
is skipped because it has already been consumed by ref
, so print
doesn't even see it. And there are no errors.All of this descends from Perl's operator precedence: from the table,
(...)
8. left + - .
9. left << >>
10. nonassoc named unary operators
11. nonassoc < > <= >= lt gt le ge
12. nonassoc == != <=> eq ne cmp ~~
(...)
where the "function call" is actually a "named unary operator" (the unary operator being ref
). So the .
operator at line 8 has higher precedence than the function call at line 10, which is why the result of print
is not the expected one. On the other hand, the function call has higher precedence than eq
(at line 12), which is why inside the if
everything works as expected.
The solution to precedence problems is to use .
A possibility is to use parentheses to emphasize the function call:
print "ref \$book_ref is a " . ref($book_ref) . "\n";
Another one, which I like less but nevertheless works, is to use parentheses to isolate the string that must be concatenated, by putting the opening bracket just before ref
:
print "ref \$book_ref is a " . (ref $book_ref) . "\n";
Another possible approach, suggested by zdim in a comment, is to use commas:
print "ref \$book_ref is a ", ref $book_ref, "\n";
When I first wrote the if
I decided to avoid the parentheses to make the code more readable. Then I copied it and didn't notice the problem. I ended up wasting 2 hours to find the bug.
The best solution seems to be the first one, because if you copy it to another place (like another print
) you are guaranteed to also copy the parentheses that prevent the problem. With the second one I probably wouldn't realize how important the parentheses are and wouldn't copy them. And the third one works only if you remember that you have to always use commas and not dots, which is not obvious and therefore error prone. So, although they work, I consider them less safe.
Other comments have also suggested to use printf
, which requires dealing with format specifiers, or expression interpolation, like print "ref \$book_ref is a ${\ ref $book_ref }\n";
, which I find harder to read.
Bottom line: always use the parentheses.
Upvotes: 7