For example, if you aren't sure whether Perl will do
what you expect it to, it will show you what Perl is really going to
do. Consider this trap we discussed earlier:
open IN, "filename" || die $!;
This looks like perfectly valid code, and indeed it compiles without
any errors, but let's see what Perl is executing:
panic% perl -MO=Deparse -e 'open IN, "filename" || die $!'
open IN, 'filename';
As you can see, the die( ) part was optimized
away. open( ) is a list operator (since it accepts
a list of arguments), and list operators have lower precedence than
the || operator. Therefore, Perl executes the
following:
open IN, ("filename" || die $!);
Since in our example we have used "filename",
which is a true value, the rest of the expression in the parentheses
above is discarded. The code is reduced to:
open IN, "filename";
at compile time. So if the file cannot be opened for some reason, the
program will never call die( ), since Perl has
removed this part of the statement.
To do the right thing you should either use parentheses explicitly to
specify the order of execution or use the low-precedence
or operator. Both examples below will do the right
thing:
panic% perl -MO=Deparse -e 'open(IN, "filename") || die $!'
die $! unless open IN, 'filename';
panic% perl -MO=Deparse -e 'open IN, "filename" or die $!'
die $! unless open IN, 'filename';
As you can see, Perl compiles both sources into exactly the same code.
Notice that if the "filename" argument is not
true, the code gets compiled to this:
panic% perl -MO=Deparse,-p -e 'open IN, "" || die $!'
open(IN, die($!));
which causes the program to die($!) without any
reason in $!:
panic% perl -e 'open IN, "" || die $!'
Died at -e line 1.
while if we do the right thing, we should see the reason for the
open( ) failure:
panic% perl -e 'open IN, "" or die $!'
No such file or directory at -e line 1.
Also consider:
panic% perl -MO=Deparse,-p -e 'select MYSTD || die $!'
select(MYSTD);
Since select( ) always returns a true value, the
right part of the expression will never be executed. Therefore, Perl
optimizes it away. In the case of select( ), it
always returns the currently selected file handle, and there always
is one.
We have used this cool -MO=Deparse technique
without explaining it so far. B::Deparse is a
backend module for the Perl compiler that generates Perl source code,
based on the internal compiled structure that Perl itself creates
after parsing a program. Therefore, you may find it useful while
developing and debugging your code. We will show here one more useful
thing it does. See its manpage for an extensive usage manual.
When you use the -p option, the output also
includes parentheses (even when they are not required by precedence),
which can make it easy to see if Perl is parsing your expressions the
way you intended. If we repeat the last example:
panic% perl -MO=Deparse,-p -e 'open IN, "filename" or die $!'
(open(IN, 'filename') or die($!));
we can see the exact execution precedence. For example, if you are
writing constructor code that can serve as a class method and an
instance method, so you can instantiate objects in both ways:
my $cool1 = PerlCool->new( );
my $cool2 = $cool1->new( );
and you are unsure whether you can write this:
package PerlCool;
sub new {
my $self = shift;
my $type = ref $self || $self;
return bless { }, type;
}
or whether you have to put in parentheses:
my $type = ref ($self) || $self;
you can use B::Deparse to verify your assumptions:
panic% perl -MO=Deparse,-p -e 'ref $self || $self'
(ref($self) or $self);
Indeed, ref( ) has a higher precedence than
||, and therefore this code will do the right
thing:
my $type = ref $self || $self;
On the other hand, it might confuse other readers of your code, or
even yourself some time in the future, so if you are unsure about
code readability, use the parentheses.
Of course, if you forget the simple mathematical operations
precedences, you can ask the backend compiler to help you. This one
is obvious:
panic% perl -MO=Deparse,-p -e 'print $a + $b * $c % $d'
print(($a + (($b * $c) % $d)));
This one is not so obvious:
panic% perl -MO=Deparse,-p -e 'print $a ** -$b ** $c'
print(($a ** (-($b ** $c))));
B::Deparse tells it all, but you probably
shouldn't leave such a thing in your code without
explicit parentheses.
Finally, let's use B::Deparse to
help resolve the confusion regarding the statement we saw earlier:
$c = $a > $b and $a < $b ? 1 : 0;
panic% perl -MO=Deparse -e '$c = $a > $b and $a < $b ? 1 : 0;'
$a < $b ? '???' : '???' if $c = $a > $b;
-e syntax OK
Just as we explained earlier, the and operator has
a lower precendence than the = operator. We can
explicitly see this in the output of B::Deparse,
which rewrites the statement in a less obscure way.