Perl Part I · Module 10 · Subroutines and Arguments
Designing a Subroutine Worth Reusing
Everything in this module has been a mechanism: how arguments arrive, how they alias, how a return value changes shape with context, how to keep private state. This chapter is about using those mechanisms deliberately. A subroutine that works in the script it was written for is easy; one that can be lifted into a second script without being rewritten requires three decisions, and the useful thing about them is that all three are made before the body is written. They are what goes in, what single shape comes out, and what happens when the input is not what you assumed. The same piece of work written with and without those decisions is measured here side by side, and the undesigned version fails in three ways that each trace back to an earlier chapter.
Intermediate16 min readPerlPart ISubroutines
Part I · Module 10 · Chapter 10.12 · Designing a Subroutine Worth Reusing
1. The Three Decisions
The three decisions that make a subroutine liftable into another script
Each decision has a default that is right most of the time, and each default is the conclusion of an earlier chapter rather than a preference:
| Decision | Default | Because |
|---|---|---|
| what goes in | one reference per container | Chapter 10.2 §1 — @_ flattens |
| named arguments past three | Chapter 10.5 §6 | |
| unpack on the first line | Chapter 10.4 §2 — @_ aliases | |
| what comes out | one hash reference | Chapter 10.8 §2 |
| never two containers | Chapter 10.8 §1 — they merge | |
bare return for nothing | Chapter 10.8 §4 | |
| on bad input | die with a reason | Chapter 8.6 §2 |
| or a third state, never a plausible value | Chapter 8.6 §6 |
2. The Undesigned Version
use strict;
use warnings;
# three decisions left unmade
sub summarise {
my @rows = @_;
my $n = 0;
my $worst;
for my $r (@rows) {
next unless $r->{slack} < 0;
$n++;
$worst = $r->{slack} if !defined $worst || $r->{slack} < $worst;
}
print "failing: $n, worst: $worst\n"; # prints instead of returning
return ($n, $worst); # and also returns a list
}
my @rows = (
{ blk => "alu", slack => -0.12 },
{ blk => "fifo", slack => 0.34 },
);
my ($count) = summarise(@rows);
printf "caller got count=%s\n", $count;
my $what = summarise(@rows);
printf "scalar context gave %s\n", $what;failing: 1, worst: -0.12
caller got count=1
failing: 1, worst: -0.12
scalar context gave -0.12It computes the right answer, and it has three defects — each of which traces to a chapter in this module.
1. It prints. Lines 1 and 3 of that output came from inside the subroutine. A caller who wants the numbers in a report, or wants to suppress them, or calls it twice — as this one does — cannot. Printing is a side effect the caller did not ask for and cannot turn off, and it appears twice because the subroutine was called twice.
2. It returns a positional list. my ($count) = summarise(@rows) works today. Add a third figure — the count of unknown rows — and every destructuring caller has to change, which Chapter 10.8 §2 measured as the cost of a positional return.
3. In scalar context it hands back the worst slack. my $what = summarise(@rows) gave -0.12, not 1. A caller writing my $n = summarise(@rows) meaning "how many failed" gets a slack value — because Chapter 10.6 §1 established that a returned list literal in scalar context gives its last element. Nothing warns.
There is a fourth problem the measurement does not show, because the input was clean: $r->{slack} < 0 on a row with no slack key compares undef, and on a row whose slack is "N/A" compares a string — Chapter 8.1 §5 measured that both numify to 0 and are therefore classified as passing.
3. The Designed Version
use strict;
use warnings;
# inputs: one array reference. returns: one hash reference. bad input: dies.
sub summarise {
my ($rows) = @_;
die "summarise: expected an array reference\n"
unless ref $rows eq "ARRAY";
my %out = (checked => 0, failing => 0, worst => undef, unknown => 0);
for my $r (@$rows) {
unless (ref $r eq "HASH" && defined $r->{slack}
&& $r->{slack} =~ /^[-+]?\d+(?:\.\d+)?$/) {
$out{unknown}++;
next;
}
$out{checked}++;
next unless $r->{slack} < 0;
$out{failing}++;
$out{worst} = $r->{slack}
if !defined $out{worst} || $r->{slack} < $out{worst};
}
return \%out;
}
my @rows = (
{ blk => "alu", slack => -0.12 },
{ blk => "fifo", slack => 0.34 },
{ blk => "regf", slack => "N/A" },
{ blk => "pad" },
);
my $s = summarise(\@rows);
printf "checked=%d failing=%d unknown=%d worst=%s\n",
$s->{checked}, $s->{failing}, $s->{unknown},
(defined $s->{worst} ? sprintf("%+.2f", $s->{worst}) : "none");
my $empty = summarise([]);
printf "empty input: checked=%d failing=%d worst=%s\n",
$empty->{checked}, $empty->{failing},
(defined $empty->{worst} ? $empty->{worst} : "none");
my $r = eval { summarise("not a ref") };
print "bad input: ", ($r ? "accepted\n" : "rejected: $@");checked=2 failing=1 unknown=2 worst=-0.12
empty input: checked=0 failing=0 worst=none
bad input: rejected: summarise: expected an array referenceSame arithmetic, and now:
- Nothing is printed by the subroutine. The caller formats, and formats differently for the two calls.
- One hash reference comes back, so context cannot matter —
my $s = summarise(...)is the only sensible call. Adding a field breaks nobody. - The two unusable rows are counted separately as
unknown, rather than being silently classified as passing. That is Chapter 8.6's three-state verdict as a return field. - The empty input degrades: zeros and
worst => undef, which the caller renders asnonerather than a fabricated+0.00— Chapter 9.9 §3's finding. - A wrong-shaped argument dies with a message naming the subroutine.
4. Decision One: What Goes In
sub f { my ($rows) = @_; ... } # one reference
sub f { my ($rows, $opts) = @_; ... } # two references
sub f { my ($rows, %opt) = @_; ... } # one reference + named options
sub f { my @values = @_; ... } # a variadic list of one kindPass containers as references, because Chapter 10.2 §1 measured that two arrays arrive as one flat run with no boundary. The only case for a bare list is a genuinely variadic argument — sum, max, longest — where position means nothing.
Unpack on the first line and never mention @_ again. Chapter 10.3 §1 established the readability reason and Chapter 10.4 §2 the stronger one: @_ aliases the caller's variables, so a later edit that writes to $_[0] reaches outside.
Switch to named arguments at three parameters, or at two optional ones — Chapter 10.5 §6 — and put the defaults first in the merge, which §2 there measured as the thing that silently ignores the caller when reversed.
Decide whether you modify what you were given, and say so in the name. A subroutine that sorts its argument in place is sort_in_place(\@rows); one that does not is sorted(\@rows) returning a new reference. Chapter 10.4 §4 and Chapter 7.13 cover which copies share what.
5. Decision Two: What Comes Out
6. Decision Three: What Happens on Bad Input
Three designs, and the choice is about who can do something about the problem.
die "summarise: expected an array reference\n" unless ref $rows eq "ARRAY";For a programming error — the wrong kind of argument, a missing required parameter. The caller passed something impossible, so there is no sensible value to return and no recovery. Die with the subroutine's name in the message, because that is what tells whoever reads the log which contract was broken.
$out{unknown}++ unless usable($r);For unusable data, which is different. The call was correct; a row in the input was not. Chapter 8.6 measured what happens when a check has no third state: a two-state verdict reported PASS for four of six unusable inputs and exited 0. Counting them separately is what makes the summary honest — checked=2 unknown=2 says something failing=1 alone does not.
return unless exists $DB{$name};For a lookup that legitimately misses. Not an error, not bad data, just absence — and the caller tests the return value.
Pick one per subroutine. A subroutine that sometimes dies and sometimes returns a status hash forces every caller to handle both, which is the thing a contract is supposed to prevent.
One more thing worth deciding explicitly: what the subroutine does with an empty input. Chapter 9.9 §3 measured that counts and lists degrade gracefully to zero and empty, while a minimum is undef and a mean is Illegal division by zero, fatal. §3's version returns worst => undef and leaves the rendering to the caller, which reports none. Returning 0 there would have been a number that was never measured.
7. A Checklist
Before writing the body:
- Inputs — one reference per container? Named arguments if more than three? Unpacked on the first line? Does it modify what it was given, and does the name say so?
- Returns — one shape from every path? A hash reference if there are parts? A bare
returnfor nothing? No sentinel inside the value range? - Bad input —
diefor a broken call, a third state for bad data, a barereturnfor an honest miss? One of the three, not two? - Empty input — what does each returned figure become? Is any of them a fabricated number?
- Name — does it say what comes back, and whether anything is modified?
Two notes on the last point, because naming carries more of the contract than it looks:
failing_blocks() # returns a list — plural says so
failing_count() # returns a number
sorted(\@rows) # returns a new thing
sort_in_place(\@rows) # modifies the argument
must_parse($path) # dies on failure
try_parse($path) # returns a statusA reader who sees failing_count() will not assign it to an array, and one who sees sort_in_place will not expect the original to survive. That is contract documentation costing no lines at all.
8. Module 10, Closed
Perl's subroutines have almost no declaration — sub, a name, a block — and the consequence runs through the whole module: the language makes very few decisions for you, so the ones you do not make explicitly are made by defaults that are rarely what you wanted.
Arguments arrive in one flat array with no names and no count check, so a wrong call is not an error — Chapter 10.2 measured an extra argument being silently discarded. They arrive as aliases, so a subroutine can write to its caller's variables with nothing visible at the call site — Chapter 10.4. And what a return means is decided by the caller afterwards, so the same statement hands back a last element or a count depending on a line in a different file — Chapter 10.6.
The traps in this module share the property that made Modules 8 and 9's worth collecting — none of them produces an error:
- An extra argument is discarded in silence, and the result is identical to a correct call.
$_[0] = ...changes the caller's variable, with nothing at the call site to show it.||=for a default destroys a deliberate0— and0is what--retries 0passes.my %args = (@_, DEFAULTS)ignores the caller entirely, and a no-argument test passes.return (10,20,30)in scalar context gives30;return @agives3— and the two are identical in list context, so list-context tests cannot tell them apart.return undefmakesif (@result)true on failure, and the caller then prints a blank row and exits 0.- Two returned containers arrive merged, with the boundary gone.
Two are worse than silent: a misspelled argument name in a named call is ignored entirely (Chapter 10.5 §5), and an undefined subroutine is a runtime error that perl -c reports as syntax OK (Chapter 10.1 §5).
Against all of that, the module's practical advice is four lines:
Unpack on the first line. Default with
//=. Return one shape. Decide what bad input does.
Each of those closes a whole class, and none costs more than a line. The rest — wantarray, prototypes, redo-style cleverness — is worth being able to read and rarely worth writing.
Module 11 turns to regular expressions, which is the largest module in Part I at twenty-two chapters, and the one where Perl's defaults are most worth understanding before relying on them.
9. Common Mistakes
Printing from inside a subroutine that computes a value. The caller cannot format, suppress or reuse it.
Returning a positional list that might grow a field. Every destructuring caller changes. Use a hash reference.
Different return shapes on different paths. The caller cannot write one handling.
Passing containers as bare lists. They merge; pass references.
Mentioning @_ after the first line. It aliases the caller's variables.
||= for a default. A deliberate 0 becomes the default.
Defaults last in a named-argument merge. The caller is silently ignored.
Mixing die and status returns in one subroutine. Pick one.
Returning 0 for a minimum over an empty list. That is a number nobody measured; return undef.
A name that does not say what comes back. rows() against row_count() is free documentation.
10. Exercise
Exercise 1 — the undesigned version. Write §2's summarise and both callers. Record all four output lines. Then name which earlier chapter explains each of the three defects.
Exercise 2 — the scalar-context defect. From Exercise 1, explain in one sentence why my $what = summarise(@rows) gave a slack value. Then change the subroutine to return @list; instead of a literal and record what scalar context now gives.
Exercise 3 — bad rows. Add { blk => "regf", slack => "N/A" } and { blk => "pad" } to Exercise 1's input, unchanged otherwise. Record the failing count and say how the two unusable rows were classified.
Exercise 4 — the designed version. Write §3's summarise. Call it with the four-row input, with an empty array reference, and with a plain string. Record all three results.
Exercise 5 — extend it. Add a corners field to the returned hash counting distinct corners. Confirm the existing caller still works without modification.
Exercise 6 — the empty case. For the empty-input call, print every returned field. Say which one is undef and what the caller should render for it. Then try to compute a mean from the returned fields and record what happens.
Exercise 7 — the three bad-input designs. Write three versions of a find_block helper: one that dies, one that returns a status hash reference, and one with a bare return. For each, write the caller. Say which you would choose for a lookup that legitimately misses.
Exercise 8 — names. For each of §7's six example names, write the one-line subroutine it implies. Then rename summarise from §3 to say what it returns.
Exercise 9 — the checklist. Take any subroutine you have written in this module's exercises and answer all five checklist items for it. Change whatever the answers say to change.
11. Summary
- Three decisions, made before the body: what goes in, what single shape comes out, what happens on bad input.
- Inputs: one reference per container, named arguments past three, unpacked on the first line, and a name that says whether the argument is modified.
- Returns: one shape from every path, a hash reference when there are parts, a bare
returnfor nothing, and never a sentinel inside the value's own range. - Bad input:
diefor a broken call, a third state for unusable data, a barereturnfor an honest miss — one of the three, not two. - Also decide the empty case. Counts degrade to zero; a minimum is
undefand a mean is fatal. - Measured, the undesigned version failed three ways: it printed from inside, it returned a positional list, and in scalar context it handed back
-0.12where the caller wanted a count — all with empty STDERR and exit 0. - The designed version returned one hash reference, counted the two unusable rows as
unknown, degraded on empty input, and rejected a wrong-shaped argument by name. - Names carry contract:
failing_blocksagainstfailing_count,sortedagainstsort_in_place,must_parseagainsttry_parse. - The module in four lines: unpack on the first line, default with
//=, return one shape, decide what bad input does.
Where this fits
Part of the Perl curriculum.
