J
J. Romano
Dear Perl community,
I was recently burned using the .. operator in a Perl program. I
initially thought it was a bug that was causing the problem, but I
eventually figured out the reason behind the "burn." Therefore, I'll
share my findings here in the hopes that it might help someone in the
future not to be burned by it like I was.
Basically, I had code that read an input file a line at a time,
processed the line, and then put the processed line into an output
file. In the loop that read each line, there was a condition that was
something like:
if ($mandatory or m/BEGIN/ .. m/END/)
{
print OUT $_;
}
The condition was supposed to mean: "If the line is mandatory or it is
between the 'BEGIN' and 'END' lines, print the line to the output
file."
Sounds simple enough.
Well, what was happening was that sometimes that condition worked
exactly as expected, but other times it always evaluated to true, even
when the line was not mandatory nor fell between BEGIN and END lines.
I thought this was a bug in Perl, because when I ran the program in
the debugger, stopped at a breakpoint set at that condition, and
evaluated the condition with a command like:
print "True" if ($mandatory or m/BEGIN/ .. m/END/);
the word "True" would not be printed. But when I hit "n" to advance
to the next line, the program counter would advance to the print
statement, as if it evaluated to true!
I was ready to call it a Perl bug until I realized that the lines:
if ($mandatory or m/BEGIN/ .. m/END/) # line 1
and
if (m/BEGIN/ .. m/END/ or $mandatory) # line 2
are NOT equivalent!
Because of a thing called "short circuit evaluation," the ..
operator may not get evaluated in line 1, whereas it will always be
evaluated in line 2. And if you are expecting the .. operator to be
evaluated when in fact it doesn't, then that can mean that you think
the .. operator will get set to "false," when in reality it never
does, and instead will continue to return "true."
That's what was happening with me. Because the .. operator was not
being evaluated when I thought it was, it never got a chance to "shut
itself off," and since I was thinking of it in terms of the current
line being between the BEGIN and END lines, I could not figure out why
it was behaving the way it did.
So why did typing the condition right into the debugger return the
behavior that I was expecting (which was opposite of the program
behavior)? Because, when I typed the line:
print "True" if ($mandatory or m/BEGIN/ .. m/END/);
into the debugger, it was evaluating that particular .. operator FOR
THE FIRST TIME. And since the current line was not mandatory nor
between the BEGIN and END lines, it correctly evaluated to false.
Although they looked identical, the .. operator I typed into the
debugger was NOT the same one as the one in the Perl script, and
therefore they kept track of their own states, independent of each
other.
Which brings me to another point to be careful of:
Anytime you type a line like this (that contains the .. or the ...
operator) in the debugger:
print "True" if m/BEGIN/ .. m/END/;
it is essentially the same as the line:
print "True" if m/BEGIN/;
because that line, when typed in the debugger, only gets evaluated
once, and therefore only the first part of the .. operator will make a
difference when evaluated. This is true even if you type that
condition into the debugger multiple times (because, according to the
interpreter, those are separate instantiations of the .. operator, and
therefore they all keep their own separate state).
Chances are, if you are using the line:
if ($mandatory or m/BEGIN/ .. m/END/)
in a Perl program you probably meant to use:
if (m/BEGIN/ .. m/END/ or $mandatory)
instead, since short-circuit evaluation won't affect the state of
$mandatory (but can definitely affect the state of the .. operator).
So be careful of using the .. and ... operators in a condition
where short-circuit evaluation is an issue. Hopefully fewer
programmers will be burned by this now that I have shared this with
you.
-- Jean-Luc
I was recently burned using the .. operator in a Perl program. I
initially thought it was a bug that was causing the problem, but I
eventually figured out the reason behind the "burn." Therefore, I'll
share my findings here in the hopes that it might help someone in the
future not to be burned by it like I was.
Basically, I had code that read an input file a line at a time,
processed the line, and then put the processed line into an output
file. In the loop that read each line, there was a condition that was
something like:
if ($mandatory or m/BEGIN/ .. m/END/)
{
print OUT $_;
}
The condition was supposed to mean: "If the line is mandatory or it is
between the 'BEGIN' and 'END' lines, print the line to the output
file."
Sounds simple enough.
Well, what was happening was that sometimes that condition worked
exactly as expected, but other times it always evaluated to true, even
when the line was not mandatory nor fell between BEGIN and END lines.
I thought this was a bug in Perl, because when I ran the program in
the debugger, stopped at a breakpoint set at that condition, and
evaluated the condition with a command like:
print "True" if ($mandatory or m/BEGIN/ .. m/END/);
the word "True" would not be printed. But when I hit "n" to advance
to the next line, the program counter would advance to the print
statement, as if it evaluated to true!
I was ready to call it a Perl bug until I realized that the lines:
if ($mandatory or m/BEGIN/ .. m/END/) # line 1
and
if (m/BEGIN/ .. m/END/ or $mandatory) # line 2
are NOT equivalent!
Because of a thing called "short circuit evaluation," the ..
operator may not get evaluated in line 1, whereas it will always be
evaluated in line 2. And if you are expecting the .. operator to be
evaluated when in fact it doesn't, then that can mean that you think
the .. operator will get set to "false," when in reality it never
does, and instead will continue to return "true."
That's what was happening with me. Because the .. operator was not
being evaluated when I thought it was, it never got a chance to "shut
itself off," and since I was thinking of it in terms of the current
line being between the BEGIN and END lines, I could not figure out why
it was behaving the way it did.
So why did typing the condition right into the debugger return the
behavior that I was expecting (which was opposite of the program
behavior)? Because, when I typed the line:
print "True" if ($mandatory or m/BEGIN/ .. m/END/);
into the debugger, it was evaluating that particular .. operator FOR
THE FIRST TIME. And since the current line was not mandatory nor
between the BEGIN and END lines, it correctly evaluated to false.
Although they looked identical, the .. operator I typed into the
debugger was NOT the same one as the one in the Perl script, and
therefore they kept track of their own states, independent of each
other.
Which brings me to another point to be careful of:
Anytime you type a line like this (that contains the .. or the ...
operator) in the debugger:
print "True" if m/BEGIN/ .. m/END/;
it is essentially the same as the line:
print "True" if m/BEGIN/;
because that line, when typed in the debugger, only gets evaluated
once, and therefore only the first part of the .. operator will make a
difference when evaluated. This is true even if you type that
condition into the debugger multiple times (because, according to the
interpreter, those are separate instantiations of the .. operator, and
therefore they all keep their own separate state).
Chances are, if you are using the line:
if ($mandatory or m/BEGIN/ .. m/END/)
in a Perl program you probably meant to use:
if (m/BEGIN/ .. m/END/ or $mandatory)
instead, since short-circuit evaluation won't affect the state of
$mandatory (but can definitely affect the state of the .. operator).
So be careful of using the .. and ... operators in a condition
where short-circuit evaluation is an issue. Hopefully fewer
programmers will be burned by this now that I have shared this with
you.
-- Jean-Luc