Zap along with the short circuit operators
To demonstrate, let's create a hypothetical emergency.(No people, animals, or electrons were harmed in the production of this article.) You have an appointment at The Perl Clinic but you arrive early and are directed to the waiting room. Four other people are waiting, and as you are sitting down one of them collapses to the floor. "I know First Aid!" you cry, and jump up only to realize that the shock has precipitated an attack of your rare medical condition leaving you not only unable to move, but unable to communicate in anything but Perl. Surprisingly the other people in the room (let's call them Tom, Dick, and Harry) do not seem fazed by this, and appear prepared to follow your lead, as long as you talk to them through the appropriate filehandle.
Few victims have walked away from CPR; all you can do as a bystander is keep them alive until the real professionals get there with drugs and machines that go ping.
print "Have you got a cellphone? "; chomp ($answer = <STDIN>); ($answer eq "y") && (print "Ring 911!\n");
This last line is probably my favorite Perl construct, and I frequently overuse it. Perl has two logical operators, && ("and") and || ("or"), that short-circuit: when evaluating their first operand, they can sometimes deduce enough to avoid evaluating their second operand. This is called lazy evaluation. When you say X && Y, Y is only evaluated if X is true. Conversely, with X || Y, Y is only evaluated if X is false. This isn't specific to Perl. It's just simple logic: X && Y is true only if both X and Y are true; X || Y is true if either X or Y is true.
In the example above, if Tom answers in the affirmative, then ($answer eq "y") is true, so the next expression (print "Ring 911!\n") is evaluated. Were $answer anything but y, the print() wouldn't be evaluated, since whether it's true or false, the entire statement must be false because of the first expression. The parentheses aren't necessary, but as with all operators it's best to play it safe unless you can keep the precedence and associativity of all the operators in your head. See the perlop documentation for more detail.
You could as well have said
if ($answer eq "y") { print "Ring 911!\n" }
which would have yielded the same result. Not only is it longer but, in some ways, not as straightforward. After all, you are perfectly used to using the word "and" for the same purpose in natural language. "Scratch my back and I'll scratch yours" sounds so much better than "If you scratch my back then I'll scratch yours," doesn't it?
A motherboard of short circuits
Adding an || lets us upgrade from an if/then to an if/then/else:
($answer eq "y") && (print "Ring 911!\n") || (print "Get one!\n");
Remember that Perl evaluates as few expressions as necessary. If Tom answers y, Perl knows that it is faced with a statement of the form "(true and Y) or Z". Provided Y is true (which it is, since print() returns 1 on success, and it's usually successful), there's no need to evaluate Z since the entire statement must be true anyway.
What if Tom doesn't have a phone? Put yourself in the interpreter's shoes:
- I have an "(X and Y) or Z" construction so ...
- ... I must evaluate X.
- ($answer eq "y") is false.
- I have a "(false and Y) or Z" construction so ...
- I have a "false or Z" construction so...
- ... I must evaluate Z.
- (print "Go find one!\n") is true, but all we care about is the side effect: displaying the text to standard output.
This is why these operators are so useful. Take the ubiquitous
open(FILE,"myfile.log") || die "Can't open file !\n";
You should now realize why this works. If the open() statement is successful (that is, myfile.log is found and is readable), Perl doesn't need to evaluate the die(), because "true or anything" is true. The last evaluated statement is open(), the file is opened and away we go. If the open() fails, that's no longer the case and the script dies.
Some of the most powerful one-liners use these operators. Some examples:
- $argument = shift || "default_value";
This sets $argument to the next command-line (or subroutine argument) if there is one, and "default_value" otherwise. It should be read as $argument = (shift || "default_value").
- print "It's a text file" if -f $file && -T _;
This succeeds if $file is a plain file (-f) and a text file to boot (-T). The symbol _ refers to the last filehandle used for a file test or stat().
- $version && (print "Version 1.42\n");
When Perl is invoked with the -s option, command-line arguments implicitly define variables of the same name. So if our program is called with a -version argument, $version will be defined and the print() statement will execute. If $version is undefined, it's necessarily false and so the statement fails before the print() statement is reached.
Off With Its Tail - An Aside
At the risk of our patient expiring from lack of attention, let me talk about the statement that grabs the input: chomp($answer = <STDIN>). The chomp() function lops off the line terminator from the end of the string - the \n resulting from Tom hitting the RETURN key. chomp() returns the number of characters that were lopped off (usually 1). So if Tom's answer to the prompt printed by our example is:
y<RETURN>
then the incorrect $answer = chomp(<STDIN>) sets $answer to 1 (on UNIX, DOS, and Windows) rather than y. chop(), which you'll still see in Perl 4 programs, has a similar potential for disaster. chop() removes the last character whether it's a line terminator or not. The proper idiom is chomp($answer = <STDIN>) (or chop($answer = <STDIN>)) which both set $answer to y. If you want to chop more than one character, use substr(), described later.
An Operator By Any Other Name
Perl 5 provides the and and or operators which work just like && and ||, but with lower precedence (and in fact the lowest precedence of all Perl operators). If you're one of those people who object to code that looks like line noise, go ahead and use them - in fact, their low precedence often saves on parentheses:
open FILE, "foo" or die "Can't open file\n";
in contrast to
open (FILE, "foo") || die "Can't open file\n";
But be careful! $argument = shift || "some_value" does the right thing, but $argument = shift or "some_value" doesn't. The first is equivalent to $argument = (shift || "some_value") and the second is equivalent to ($argument = shift) or "some_value"). When in doubt, parenthesize.
Loop The Loop
While Tom is off contacting the ladies and gentlemen of the ambulance service, Dick and Harry have established that the patient is in arrest (no pulse) and is apnic (not breathing). As it is unlikely that our unfortunate victim choked or drowned to death, there is only one course of action - CPR. Harry seems to knows how to perform rescue breathing, but Dick is looking a bit uncertain.
while ($pulse ne "y") { print "Do 3 sets of 5 chest compressions\n"; print "Any sign of a pulse? "; chomp($pulse = <STDIN>); } print "Yippee!\n";
We've set up a while loop with an exit condition ("is there a pulse?"). This is a simple construct with one downside - if our prostrate friend is on the way to her next incarnation, Dick will be left doing chest compressions until his batteries run down, and for no good reason. In fact exhaustion is one of the four sanctioned reasons for ceasing CPR.**
The others being:
- the patient revives
- the patient dies
- you are relieved by qualified personnel
What we really want to do is keep going until Dick is tired, but to stop as soon as we get a pulse. The usual way of exiting a loop is via last:
while ($pulse ne "y") { print "Do 3 sets of 5 chest compressions\n"; print "Any sign of a pulse? "; chomp($pulse = <STDIN>); last if $compressions++ > 500; }
As you can see, last terminates a loop immediately. Other loop control statements are next (which skips the rest of the block - the statements inside the curly braces) and begins the next execution), redo (which restarts the loop without testing the conditional) and continue (which identifies a block to be executed before the loop is re-entered).
Labels make it possible to move around between loops:
CPR: while ($tired ne "y") { # outer loop print "Do 3 sets of 5 chest compressions\n"; foreach (1..3) { # inner loop print "Any sign of a pulse? "; chomp($pulse = <STDIN>); last CPR if $pulse eq "y"; print "Are you too tired? "; chomp($tired = <STDIN>); } }
One Statement To Rule Them All
Finally let's not forget our final
print "Yippee!\n"
If we've exited the loop because poor Dick has collapsed with exhaustion next to a corpse, crowing with triumph might be considered tactless. But we can discern how we exited the loop - if it was because of fatigue, we know that $tired must be y. Let's make our conclusion more appropriate:
$message = (($tired eq "y") ? "Too bad\n" : "Yippee!\n"); print $message, "\n";
or even better
print (($tired eq "y") ? "Too bad\n" : "Yippee!\n");
This construct, which we visited briefly last issue, is the conditional operator ?: which is trinary (takes three operators) and behaves identically to:
if ($tired eq "y") { print "Too bad\n"; } else { print "Yippee!\n"; }
A particularly yummy (or maybe perverse) use of this operator arises from the fact that you can actually assign to it. Consider this code:
$we = $undertaker = 'sad'; print "How is the patient? "; chomp($patient = <STDIN>); ($patient eq "ok" ? $we : $morgue) = 'happy'; print "Patient is $patient, we are $we, undertaker is $morgue\n";
where we assign happy to either $we or $morgue depending on whether the patient is okay. Running it yields:
How is the patient? ok Patient is ok, we are happy, undertaker is sad
or, if we're unlucky:
How is the patient? dead Patient is dead, we are sad, undertaker is happy
Great Value, Low Prices
This works because you can assign to ?: when both the second and third arguments are lvalues. The term "lvalue" sounds technical, but it's just a fancy word for "Something you can assign a value to." The "l" derives from the fact that lvalues usually appear on the left hand of an equals sign; the opposite of an lvalue is an rvalue. Nearly all variables are lvalues, but this is Perl, where the obvious is never enough:
- Assignment, under some circumstances, can be thought of as an lvalue. When you say chomp($answer = <STDIN>), the assignment $answer = <STDIN> is an lvalue that can be chomped just like a variable.
- Various functions are lvaluable. For example, you can use substr() in an assignment if its first argument is an lvalue:
$exclaim = "No way!\n"; print $exclaim; substr($exclaim,0,2) = "Yes"; print $exclaim;
displays
No way! Yes way!because we assigned Yes to the first two characters of $exclaim , which previously held No . Note that $exclaim stretched itself to accommodate a longer string without fuss.
Nothing Like A Happy Ending
Needless to say, Tom arrived with the cavalry and our patient is in safe hands, while everybody celebrates Perl's medicinal qualities. Incidentally, never perform CPR on a victim if they are breathing, since that means they have a pulse and their heart is beating. If you do, you can cause cardiac arrest.
See https://www.catt.citri.edu.au/emergency/docs/jokes.html for the story of the ambulance driver who arrived to find a bystander performing CPR on a woman with a broken ankle who yelled "Ow!" at each compression.
Further Reading
- the perlsyn documentation
- ftp://www.perl.org/CPAN/doc/FMTEYEWTK/switch_statements
- https://www.medaccess.com/first_aid/FA_TOC.htm Frossie Economou is an astronomer who battles software (and users) at the Joint Astronomy Centre, Hawaii, in order to fund her book habit and feed her fish.
Frossie Economou is an astronomer who battles software (and users) at the Joint Astronomy Centre, Hawaii, in order to fund her book habit and feed her fish.