Skip to content
VLSI Mentor

Perl Part I · Module 10 · Subroutines and Arguments

Named Arguments With a Hash

A positional argument list stops working well at about three parameters, and stops working at all once two of them are optional — because a caller who wants to set only the third has to pass undef as a placeholder for the second. The alternative is to pass name-and-value pairs and collect them into a hash, which lets the caller supply any subset in any order and makes a long call self-documenting at the point where it is read. It costs one line inside the subroutine, and that line has an ordering requirement that is easy to get backwards. Getting it backwards does not fail: it silently ignores everything the caller asked for.

Intermediate15 min readPerlPart ISubroutines

Part I · Module 10 · Chapter 10.5 · Named Arguments With a Hash

1. Pairs In, Hash Out

Azvya Education Pvt. Ltd.VLSI Mentor
named.pl
use strict;
use warnings;

sub report {
    my %args = (corner => "ss", limit => 0, verbose => 0, @_);
    return sprintf("corner=%s limit=%s verbose=%s",
        $args{corner}, $args{limit}, $args{verbose});
}

print report(),                            "\n";
print report(limit => 5),                  "\n";
print report(verbose => 1, corner => "ff"), "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
corner=ss limit=0 verbose=0
corner=ss limit=5 verbose=0
corner=ff limit=0 verbose=1

Three calls: none, one argument, two in the other order. Each got exactly what it asked for and the defaults for everything else.

The mechanism is two rules already established, combined:

  1. A hash flattens to its pairs when passed — Chapter 10.2 §1 measured count(%h) as 4. So report(limit => 5) arrives in @_ as the two elements ("limit", 5).
  2. Assigning a list to a hash pairs it up, and a later key wins over an earlier one — Chapter 6.10 §5's rule.

So my %args = (DEFAULTS, @_) builds the defaults first and lets the caller's pairs overwrite any of them. One line, and the whole interface is declared in it.

=> is the thing that makes this readable. It quotes the bareword on its left — Chapter 6.2's finding — so corner => "ff" needs no quotes on the name and reads as a setting rather than a pair of values.

PositionalNamed
report("alu", "ff", 5)report(block => "alu", corner => "ff", limit => 5)
order is load-bearingorder is irrelevant
skipping one needs undefskip by omission
the call site needs the docsthe call site is the docs
shorterlonger, and self-explaining

2. The Ordering Requirement

3. Two Styles, and When to Use Each

Azvya Education Pvt. Ltd.VLSI Mentor
Collect into a hash, or into a hash reference
sub by_hash { my %args = (corner => "ss", @_);        ... }
sub by_ref  { my ($args) = @_; $args->{corner} //= "ss"; ... }
Azvya Education Pvt. Ltd.VLSI Mentor
The call sites differ
by_hash(corner => "ff", limit => 5);
by_ref({ corner => "ff", limit => 5 });     # note the braces

The hash form is the conventional one and is what most Perl uses. The reference form has one real advantage — the caller's pairs are visibly grouped, so a call that also takes positional arguments stays unambiguous:

Azvya Education Pvt. Ltd.VLSI Mentor
Where the reference form earns it
compare($left, $right, { tolerance => 0.01, verbose => 1 });

With the hash form, compare($left, $right, tolerance => 0.01) works too, but the subroutine must shift the two positional arguments off before collecting the rest — and Chapter 10.3 §3 measured that mixing shift with an arity check on @_ is where counts go wrong.

Default to the hash form. Reach for the reference form when a subroutine has both positional and named arguments, which is the one case where the braces are doing real work.

Note the //= in by_ref: it defaults a key in place, and Chapter 10.3 §2 measured why it must be //= and not ||=. With the hash form the defaults-first list does the same job for every key at once, which is why it is the shorter pattern.

4. An Odd Number of Pairs

Azvya Education Pvt. Ltd.VLSI Mentor
named-odd.pl
use strict;
use warnings;

sub f { my %args = @_; return scalar(keys %args) }

print "keys: ", f(a => 1, "b"), "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — STDOUT
keys: 2
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — STDERR
Odd number of elements in hash assignment at named-odd.pl line 4.
Azvya Education Pvt. Ltd.VLSI Mentor
Measured exit status
0

A warning, a hash with two keys, and exit 0. The trailing "b" became a key whose value is undef — Chapter 6.2 §4 measured the same behaviour for a plain hash assignment.

The important detail is where the warning points. It names the line inside the subroutine — line 4, the my %args = @_; — but the mistake is at the call site, which is line 6. For a subroutine called from thirty places, that message tells you which subroutine and not which caller.

Two defences:

Azvya Education Pvt. Ltd.VLSI Mentor
Report the count yourself
sub f {
    die "f: expected name/value pairs, got " . scalar(@_) . " element(s)\n"
        if @_ % 2;
    my %args = (corner => "ss", @_);
    ...
}

@_ % 2 is true for an odd count. Dying with the count at least tells you the shape was wrong; adding the caller's identity needs caller(), which Module 12 covers.

The commonest cause of an odd count, incidentally, is not a miscount but a stray comma or a missing => in a long call — which is an argument for keeping named calls to one pair per line once there are more than three.

5. Rejecting Names You Do Not Know

A positional call that passes too many arguments silently discards the extras — Chapter 10.2 §4. A named call has the same hole, and a worse version of it: a misspelled name is silently ignored.

Azvya Education Pvt. Ltd.VLSI Mentor
named-strict.pl
use strict;
use warnings;

sub strict_args {
    my %args = (corner => "ss", @_);
    my @known = qw(corner limit);
    my @bad = grep { my $k = $_; !grep { $_ eq $k } @known } sort keys %args;
    die "unknown argument(s): @bad\n" if @bad;
    return "ok";
}

print strict_args(corner => "ff"), "\n";
my $r = eval { strict_args(cornor => "ff") };
print $r ? "accepted\n" : "rejected: $@";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
ok
rejected: unknown argument(s): cornor

Without that check, strict_args(cornor => "ff") would have run with corner at its default ss and the caller's intent discarded — the same shape as Chapter 10.3 §2's ||= bug, arriving from a different direction.

This check is worth the four lines for any subroutine other scripts call. The reason is specific to named arguments: with positional arguments a caller mistake usually shows up as a missing or extra value, which defined checks catch. A misspelled name produces a complete, well-formed call where one setting is quietly at its default — and the symptom is "the tool ignores that option", which gets investigated in the option parser rather than here.

A shorter form using a hash for the known set:

Azvya Education Pvt. Ltd.VLSI Mentor
The same check, less nesting
my %known = map { $_ => 1 } qw(corner limit verbose);
my @bad   = grep { !$known{$_} } sort keys %args;
die "unknown argument(s): @bad\n" if @bad;

map { $_ => 1 } builds a set — Chapter 6.9's idiom — and the grep then needs no inner loop. Note sort keys so the error message is reproducible, which Chapter 6.5 requires of anything printed.

6. When to Switch From Positional

The decision is worth making deliberately rather than by drift.

Stay positionalSwitch to named
one or two argumentsthree or more
every argument is requiredtwo or more are optional
the order is obvious from the namethe order is arbitrary
slack_of($block, $corner)report(block => ..., corner => ..., limit => ..., verbose => ...)

The sharpest signal is a caller passing undef as a placeholder. report("alu", undef, 5) means the interface has outgrown positions — Chapter 10.3 §2 showed that //= makes it work, but a caller writing undef to skip a parameter is doing bookkeeping the subroutine should be doing.

The second signal is a call that needs a comment to be read:

Azvya Education Pvt. Ltd.VLSI Mentor
Positional, needing a comment
check($rows, 1, 0, 1);          # recurse, quiet, fail-fast

check($rows, recurse => 1, verbose => 0, fail_fast => 1);

The named version is longer and needs no comment, which on balance is the better trade for anything that will be read more than once.

7. Why This Matters in Engineering Work

Flow scripts accumulate options. A report generator starts with two parameters and ends with eight — corner, limit, verbosity, output path, whether to include waivers. Positional arguments make every one of those a position someone has to count, and adding a parameter in the middle silently reassigns every call.

Named arguments make a call reviewable. generate(corner => "ss", include_waived => 1) can be checked against intent by someone who has never read the subroutine. generate("ss", 1) cannot.

The defaults-order mistake is the one to watch for in review. It produces a subroutine that ignores its arguments entirely, for every caller, with no warning — and the obvious test, which calls it with no arguments, passes.

And the unknown-name check catches the mistake that otherwise reaches users. A typo'd option name in a wrapper script is reported as "the tool ignores --cornor", which is a true statement about the wrong layer.

8. Common Mistakes

my %args = (@_, DEFAULTS); The defaults win and the caller is ignored, silently. Defaults go first.

No unknown-name check on a shared subroutine. A misspelled name is silently ignored.

Assuming the odd-count warning names the caller. It names the line inside the subroutine.

||= to default a single key. It overwrites a deliberate 0 — Chapter 10.3 §2.

Mixing positional and named without braces. Use a hash reference for the named part.

Named arguments for a two-parameter subroutine. It is longer for no benefit.

Forgetting sort keys in the error message. Hash order varies between runs.

Staying positional once a caller writes undef as a placeholder. That is the signal to switch.

9. Exercise

Exercise 1 — the pattern. Write report from §1 with three defaulted keys. Call it with no arguments, with one, and with two in reverse order. Record all three lines.

Exercise 2 — the ordering bug. Write the same subroutine with my %args = (@_, corner => "ss");. Call it with corner => "ff" and record the result, STDERR and the exit status.

Exercise 3 — the test that misses it. Write a test for Exercise 2's broken version that calls it with no arguments and checks the default. Confirm the test passes. Then write the test that catches it.

Exercise 4 — the reference form. Rewrite report to take a hash reference, defaulting each key with //=. Call it with braces. Then write a subroutine taking two positional arguments plus a named hash reference.

Exercise 5 — odd count. Call a named-argument subroutine with (a => 1, "b"). Record the warning, which line it names, the key count, and the exit status. Then add an @_ % 2 check and record the new message.

Exercise 6 — unknown names. Write §5's check and call the subroutine with a correct name and a misspelled one. Record both. Then rewrite the check using a %known set built with map.

Exercise 7 — the undef placeholder. Write a three-parameter positional subroutine where the middle one is optional, and call it setting only the third. Then rewrite it with named arguments and compare the two call sites.

Exercise 8 — when not to. Take slack_of($block, $corner) and rewrite it with named arguments. Write one sentence on whether that is an improvement and why.

10. Summary

  • Pass name-and-value pairs and collect them with my %args = (DEFAULTS, @_); — one line declares the whole interface.
  • It works because a hash flattens to its pairs on the way in, and a later key wins in a list assigned to a hash.
  • Defaults go FIRST. my %args = (@_, DEFAULTS); silently ignores the caller — measured, corner => "ff" produced ss, empty STDERR, exit 0, for every call.
  • The test that catches that must pass a non-default value; a no-argument test passes on the broken version.
  • Callers may supply any subset in any order, and => quotes the name so the call reads as settings.
  • An odd number of elements warns and still builds the hash, with the last key undef — and the warning names the line inside the subroutine, not the caller. Check @_ % 2 yourself.
  • A misspelled argument name is silently ignored. Check keys %args against a known set for anything other scripts call.
  • Use a hash reference when a subroutine takes positional and named arguments.
  • Switch from positional at three arguments, or at two optional ones — and immediately if a caller is writing undef as a placeholder.

Chapter 10.6 turns to the other end of a subroutine: what return actually hands back, and why the same return statement produces different answers depending on how the caller asked.

Where this fits

Part of the Perl curriculum.