Perl from the ground up

Decisions, and the == against eq trap

If, elsif and else, the unless that reads backwards on purpose, what Perl treats as false, and the two sets of comparison operators that must not be mixed up.

Part 5 of 12

Intermediate17 min readBack to Courses

Asking a question

An if takes a condition in parentheses and a block of code in braces: if ($score >= 90) { ... }. When the condition is false the block is skipped, elsif offers another question, and else catches whatever is left. The braces are required even when the block is one line long.

A statement modifier is the other way to say the same thing: print "pass\n" if $score >= 70; puts the condition after the action. It reads well when both halves are short, and the long form is what you want the moment the block needs a second line.

Text and numbers compare differently

Perl keeps two sets of comparison operators, and using the wrong one is the classic mistake in the language. ==, !=, <, > and <= compare numbers. eq, ne, lt, gt and le compare text.

The trap is real and it is quiet. "7.0" == "7" is true, because both sides are read as numbers, while "7.0" eq "7" is false, because they are different text. Worse, "apple" == "banana" compares two zeroes and comes out true. Say which kind of value you are comparing — numbers with ==, text with eq — and the trap never opens.

What counts as false

Only a few values are false: the number 0, the empty piece of text, the text "0", and undef. Everything else is true, including the word "false". There is no separate true-and-false type; a condition simply tests the value it is given.

That is why unless is there: unless ($name) { ... } runs when the value is empty or missing. It reads as the question you actually have in mind, and it is worth using once per paragraph of code — unlesses nested inside unlesses are the hardest Perl to read.

Conditions are expressions, and they short-circuit

&& and || stop as soon as the answer is known. For && that means the right side is not evaluated when the left is false; for || it is skipped when the left is true. The value of the whole expression is the last value that was evaluated, which is why my $name = $input || 'guest'; can supply a default.

That default has a sharp edge: || treats 0 and the empty string as false, so a real count of 0 is replaced too. // tests only for undef, which makes it the right operator when 0 is a legitimate value. and and or are the same ideas with lower precedence, which is why they pair well with assignment. The short-circuit is not an optimisation to rely on for side effects, but it is reliable enough to build a default on.

The first true branch wins

An if/elsif/else chain is read from the top. The first condition that is true runs its block, and every later condition is skipped, so the order of the tests is part of the logic. Put the narrowest test first: a score of 95 should meet the 90 test before it can meet a broader 70 test.

unless is an if with the test reversed, and it reads best for a guard or a missing value. A ternary ? : chooses between two values rather than running two blocks, so it belongs on the right side of an assignment. Both forms are shorter ways to say something the long form already says clearly.

A comparison is a value

A comparison is not a special kind of statement; it is an expression that produces true or false, and that value can be stored: my $passing = $score >= 70;. The if then tests a name rather than repeating the comparison, which is worth doing when the same question is asked twice and the answer should not drift between the two places.

Branches, both kinds of comparison, and the false values

use strict;
use warnings;

my $score = 88;
if ($score >= 90) {
  print "A\n";
} elsif ($score >= 70) {
  print "B\n";
} else {
  print "C\n";
}

my $name = '';
unless ($name) { print "no name given\n"; }
print "pass\n" if $score >= 70;

my $left = '7.0';
my $right = '7';
print "number: ", ($left == $right ? 'yes' : 'no'), "\n";
print "text:   ", ($left eq $right ? 'yes' : 'no'), "\n";

foreach my $value (1, '', '0', 'yes') {
  my $shown = $value eq '' ? 'empty' : $value;
  print "$shown is ", ($value ? 'true' : 'false'), "\n";
}

Grades, and two comparisons of the same pair

Run it and check the grade for each score. Then change 60 to 80, add a fourth score of your own, and predict the grade before you press Run.

Output
Click "Run" to execute your code

A single equals sign is an assignment, not a comparison. Perl will put the value into your variable and then test it, and only use warnings; tells you that you meant to write two. That is one more reason those two lines sit at the top of every file.

Three mistakes hide inside ordinary-looking conditions. Using == on text compares two numbers, so "apple" == "banana" is true because both become 0. A default with || replaces a legitimate 0 or empty string; use // when only undef should trigger it. And if ($x = 5) assigns before it tests, which is why warnings are worth keeping on.

Try these next

  • Classify a temperature as cold, mild or hot, and check which branch wins at exactly 10 and 25.
  • Write two defaults for a missing count, one with || and one with //, and call each with 0.
  • Rewrite a nested if as an elsif chain and decide which version reads more clearly.

Shipping bands and a missing country

Run it and check the band and the note for each order. Then add a fourth item with a country you have not seen and a weight above the largest band, and predict both columns before you run it.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

You want to know whether a name is the text Ada. Which comparison is right?