Perl Part I · Module 8 · Conditional Statements
Statement Modifiers: Conditions After the Action
Perl lets you put the condition at the end of a statement instead of wrapping the statement in a block. This is the form you will read most often in real scripts, because the dominant use of a conditional inside a loop is a guard that skips a line, and a guard written this way takes one line instead of three. There are five modifiers, not two, and three restrictions worth knowing before you lean on them: the statement cannot be a block, two modifiers cannot be stacked, and one specific combination — declaring a variable with a modifier attached — compiles silently, runs, produces the answer you hoped for, and is documented by Perl's own reference as undefined behaviour.
Foundation16 min readPerlPart IControl FlowConditionals
Part I · Module 8 · Chapter 8.3 · Statement Modifiers: Conditions After the Action
1. Five Modifiers, Not Two
Chapter 3.9 introduced the form and the or die idiom built on it. The complete set is wider than most people realise.
use strict;
use warnings;
my $n = 5;
print "if fired\n" if $n > 3;
print "unless fired\n" unless $n > 9;
my $i = 0;
print "while $i\n" while $i++ < 2;
my $j = 0;
print "until $j\n" until ++$j > 2;
print "for $_\n" for 1 .. 2;if fired
unless fired
while 1
while 2
until 1
until 2
for 1
for 2| Modifier | Runs the statement |
|---|---|
EXPR if COND; | once, when COND is true |
EXPR unless COND; | once, when COND is false |
EXPR while COND; | repeatedly, while COND is true |
EXPR until COND; | repeatedly, until COND becomes true |
EXPR for LIST; | once per item, with $_ set |
foreach is accepted as a synonym for for in the modifier position, exactly as it is for the block form.
The for modifier is the most useful of the three loop forms:
print "$_\n" for sort keys %report;
$total += $_->{paths} for @rows;$_ is the implicit loop variable — Module 9 covers it properly, and here it is simply what for sets. The second line is an accumulation in one statement, which is a genuinely good use because there is nothing worth naming.
2. Restriction One: No Block
use strict;
use warnings;
{ print "a\n" } if 1;syntax error at modblock.pl line 4, near "if 1"
Execution of modblock.pl aborted due to compilation errors.255A modifier attaches to a single statement, and a block is not a statement. There is no way to write a two-statement body in the postfix form — the moment you need two things done conditionally, you need the block form from Chapter 8.1.
This is a useful limit rather than an annoyance. It means a postfix guard is always exactly one action, which is why the form stays readable: you never have to scan for where the conditional body ends.
3. Restriction Two: No Stacking
use strict;
use warnings;
my $n = 5;
print "both\n" if $n > 3 unless $n > 9;syntax error at modstack.pl line 6, near "3 unless"
Execution of modstack.pl aborted due to compilation errors.255One modifier per statement. Combine the conditions instead:
print "both\n" if $n > 3 && $n <= 9;The same applies to mixing kinds — you cannot write print $_ for @list if $flag;. Put the guard outside in a block, or fold it into one condition.
4. Restriction Three: my With a Modifier Is Undefined
This one does not fail. That is the problem.
use strict;
use warnings;
my $x = 5 if 1;
print "x = ", (defined $x ? $x : "undef"), "\n";x = 50It compiled, ran, printed the value you would hope for, and warned about nothing. Both use strict and use warnings were on.
And Perl's own reference says not to do it. From perldoc perlsyn, captured verbatim:
NOTE: The behaviour of a "my", "state", or "our" modified with a
statement modifier conditional or loop construct (for example, "my $x if
...") is undefined. The value of the "my" variable may be "undef", any
previously assigned value, or possibly anything else. Don't rely on it.
Future versions of perl might do something different from the version of
perl you try it out on. Here be dragons.5. The Dominant Real Use: next and last
Most statement modifiers you read are attached to a loop-control keyword.
use strict;
use warnings;
for my $n (1 .. 6) {
next if $n % 2;
last if $n > 4;
print "$n ";
}
print "\n";2 41, 3 and 5 were skipped by next if $n % 2 — an odd number leaves a remainder of 1, which is true. 6 ended the loop via last if $n > 4, so 6 never printed. Module 9 owns next and last; what matters here is the shape, because it opens almost every parsing loop:
while (my $line = <$fh>) {
next if $line =~ /^\s*$/; # blank
next if $line =~ /^\s*#/; # comment
chomp $line;
...
}Each guard is one line and reads as a sentence. The alternative is nesting, and §6 measures the difference rather than asserting it.
6. Where Postfix Stops Helping
Both forms produce the same answer, so the choice is readability — which is worth demonstrating on identical input rather than arguing about.
use strict;
use warnings;
my @rows = (
"alu -0.12",
"",
"# a comment line",
"fifo 0.34",
"regf",
);
my $kept = 0;
for my $line (@rows) {
next if $line =~ /^\s*$/;
next if $line =~ /^\s*#/;
my ($blk, $slack) = split ' ', $line;
next unless defined $slack;
$kept++;
printf " %-5s %+.2f\n", $blk, $slack;
}
printf "kept %d, skipped %d of %d\n", $kept, scalar(@rows) - $kept, scalar(@rows); alu -0.12
fifo +0.34
kept 2, skipped 3 of 5The same logic nested:
for my $line (@rows) {
if ($line !~ /^\s*$/) {
if ($line !~ /^\s*#/) {
my ($blk, $slack) = split ' ', $line;
if (defined $slack) {
$kept++;
printf " %-5s %+.2f\n", $blk, $slack;
}
}
}
} alu -0.12
fifo +0.34
kept 2, skipped 3 of 5Identical output, confirmed by diff rather than by reading. The postfix version reads top to bottom as a list of reasons to skip a line, with the real work at the left margin. The nested version pushes the real work four levels deep and makes the reader match closing braces to work out which condition guards what.
That is the case for postfix: it is flat. And that is also its limit:
| Postfix is clearer when | Block form is clearer when |
|---|---|
| the action is one short statement | the body has two or more statements |
| the condition is short | the condition needs && and || |
it is a guard — next, last, return, die | there is an else |
| the action is the point, the condition a footnote | the condition is the point |
7. do { } while Is Not a Statement Modifier
This looks like the while modifier and behaves differently, which earns one measurement.
use strict;
use warnings;
my $i = 10;
do { print "ran once at $i\n" } while ($i < 5);
my $k = 10;
while ($k < 5) { print "never\n" }
print "the plain while ran zero times\n";ran once at 10
the plain while ran zero timesThe do block ran even though the condition was false from the start. The plain while loop with the same condition ran zero times.
The reason is that this is not the modifier form. It is a do BLOCK, which is an expression, with a while modifier attached — and because a modifier runs its statement and then tests, the block executes once before the condition is evaluated at all. §2 established that a bare block cannot take a modifier; do { } can, precisely because it is an expression rather than a block statement.
Two consequences:
do { } whileis the test-at-the-bottom loop, Perl's equivalent of C'sdo ... while. Use it when the body must run at least once — reading until a sentinel, retrying an operation.nextandlastdo not work inside it, because it is not a real loop. That is a genuine trap; the fix is a properwhileloop with the first iteration handled explicitly.
8. Why This Matters in Engineering Work
It is the shape of every parsing loop. Skip blank lines, skip comments, skip rows missing a field, then do the work. Written postfix those guards are four flat lines; written nested they are four levels of indentation with the real work at the bottom — and §6 confirmed the outputs are identical, so the only thing you are choosing is whether the next person can read it.
or die is this same mechanism. Chapter 3.9 owns the idiom, including why it must be or rather than || — a precedence trap where the script silently never dies and exits 0. open ... or die is the single most common statement modifier in Perl, so that chapter is worth rereading alongside this one.
And the my case is a specification hazard rather than a bug you can test for. It works today on this Perl, both attempts to break it came out clean, and perlsyn still says the value may be anything. Splitting it into two lines costs nothing and removes the dependency entirely.
9. Common Mistakes
Attaching a modifier to a block. { ... } if COND; is a syntax error, exit 255. Use the block form.
Stacking two modifiers. One per statement; combine the conditions with &&.
my $x = ... if COND; Undefined behaviour per perlsyn, and it will not warn. Declare first, assign second.
Expecting next/last to work inside do { } while. It is not a loop.
Expecting do { } while to test before the first run. It always runs the body once.
A postfix guard that wraps onto a second line. It has lost its advantage; use the block form.
|| die instead of or die. A precedence trap — Chapter 3.9 measured that it exits 0 and never dies.
10. Exercise
Exercise 1 — all five. Write one statement for each modifier — if, unless, while, until, for — and confirm you get the eight lines from §1. Then rewrite the for one as foreach and confirm the output is unchanged.
Exercise 2 — the accumulation idiom. Build an array of three hash references with a numeric field. Total that field with $total += $_->{field} for @rows;. Then print every key of a hash with print "$_\n" for sort keys %h;.
Exercise 3 — no block. Write { print "a\n" } if 1; and record the exact error and exit status. Then write the same thing two ways that do work: as a block if, and as a single postfix statement.
Exercise 4 — no stacking. Write print "both\n" if $n > 3 unless $n > 9; and record the error. Then write one combined condition that produces the intended behaviour, and test it with $n of 2, 5 and 12.
Exercise 5 — the undefined one. Write my $x = 5 if 1; and record STDOUT, STDERR and the exit status. Then put the same construct in a subroutine, call it five times alternating a true and false argument, and record every result. Write two sentences: one on what you observed, one on why observing it proves nothing.
Exercise 6 — the fix. Rewrite Exercise 5's subroutine with the declaration and the assignment on separate lines. Confirm the results and state what is now guaranteed that was not before.
Exercise 7 — the guard stack. Take §6's five input rows and write the filter with four postfix guards. Then write it again with nested if blocks. Run both, diff the outputs, and confirm they match. Say which you would put in a shared script and give one reason.
Exercise 8 — do { } while. Run §7's example and confirm the do block runs with a false condition while the plain while does not. Then put a last inside the do block and record exactly what happens.
11. Summary
- There are five modifiers:
if,unless,while,until, andfor/foreach. EXPR for LIST;sets$_per item and is the neatest way to write a one-line accumulation or dump.- A modifier attaches to one statement, and a block is not a statement —
{ ... } if COND;is a syntax error, exit 255. - Modifiers cannot be stacked. Combine the conditions instead.
my $x = ... if COND;is undefined behaviour perperlsyn. It compiled, ran, printed5and warned nothing on Perl 5.34.1, and two attempts to reproduce the documented misbehaviour both came out correct. Declare on one line, assign on the next.- The dominant real use is
next if/last ifinside a loop, which is why nearly every parsing loop opens with a stack of them. - Postfix and nested forms give identical output — verified with
diff. The only question is whether the action or the condition is the point. - If the line wraps, use the block form.
do { } whileis not a modifier — the body runs once before the condition is tested, andnext/lastdo not work inside it.or dieis this mechanism; Chapter 3.9 owns it, including why|| diesilently never fires.
Chapter 8.4 moves the condition inside an expression, so a value can be chosen where it is used instead of assigned in two separate branches.
Where this fits
Part of the Perl curriculum.
