Skip to content
VLSI Mentor

Perl Part I · Module 10 · Subroutines and Arguments

Unpacking Arguments

Perl gives you the arguments in an array and leaves the naming to you, which means the first line of a subroutine is a small design decision rather than boilerplate. There are two established ways to write it and they are not interchangeable: one copies the arguments and leaves the original array untouched, the other removes them as it goes. After naming comes defaulting, and that is where a single character decides whether a caller can deliberately pass zero. The operator most people reach for first silently replaces a configured zero, an empty string and a missing argument with the same value — which is fine until one of those three is a real setting.

Foundation15 min readPerlPart ISubroutines

Part I · Module 10 · Chapter 10.3 · Unpacking Arguments

1. Two Ways to Name Them

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

sub list_assign {
    my ($block, $corner) = @_;
    return "list:  block=$block corner=$corner  (\@_ still has " . scalar(@_) . ")";
}

sub shift_style {
    my $block  = shift;
    my $corner = shift;
    return "shift: block=$block corner=$corner  (\@_ now has " . scalar(@_) . ")";
}

print list_assign("alu", "ss"), "\n";
print shift_style("alu", "ss"), "\n";
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
list:  block=alu corner=ss  (@_ still has 2)
shift: block=alu corner=ss  (@_ now has 0)

Both named the parameters correctly. The difference is the last column: list assignment copies and leaves @_ intact; shift consumes it.

Form@_ afterwardsReads as
my ($a, $b) = @_;unchanged"these are my parameters"
my $a = shift; my $b = shift;empty"take the next one, then the next"

Prefer the list-assignment form, for three reasons:

  • One line names everything, so the subroutine's interface is a single line a reader can take in at once. Four shift statements are four lines that have to be read in order.
  • @_ stays available. That matters if anything later in the body wants scalar(@_) for an arity check, or wants to forward the arguments onward.
  • Order is explicit rather than positional-by-execution. With shift, inserting a line in the wrong place silently reassigns every parameter after it.

shift earns its place in exactly one spot, and it is a convention worth recognising rather than fighting:

Azvya Education Pvt. Ltd.VLSI Mentor
The one idiomatic shift
sub method {
    my $self = shift;              # take the invocant
    my ($block, $corner) = @_;     # then name the rest
    ...
}

Object methods receive the object as the first argument, and my $self = shift; separates that one structural argument from the real parameters. Outside that pattern there is no reason to prefer it.

2. Defaults, and the Operator That Matters

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

sub report {
    my ($block, $corner, $limit) = @_;

    die "report: block is required\n" unless defined $block;
    $corner //= "ss";
    $limit  //= 0;

    return sprintf("block=%s corner=%s limit=%s", $block, $corner, $limit);
}

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

Five calls covering the cases that matter:

  • Omitted arguments take the default — report("alu") got ss and 0.
  • An explicit undef also takes the default (row 4). report("alu", undef, 5) is how a caller says "default this one, set that one", and //= handles it.
  • An explicit 0 survives (row 5). limit => 0 stayed 0 rather than becoming the default 0... which in this example happens to be the same value. §3 is where that stops being a coincidence.

The shape of that first line is worth adopting as a template:

Azvya Education Pvt. Ltd.VLSI Mentor
The template
sub name {
    my ($required, $optional_a, $optional_b) = @_;     # 1. name them
    die "name: required is required\n"                 # 2. check what must be there
        unless defined $required;
    $optional_a //= "default";                         # 3. default the rest
    $optional_b //= 0;
    ...
}

Required first, optional after, in the order a caller would naturally supply them. A positional parameter list with an optional argument in the middle forces callers to pass undef as a placeholder, which is the signal that it is time for Chapter 10.5's named form instead.

3. Checking What You Were Given

Chapter 10.2 §4 measured that Perl checks nothing. Three levels of check, in increasing strictness:

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

sub exactly_two {
    die "exactly_two needs 2 arguments, got " . scalar(@_) . "\n" unless @_ == 2;
    my ($a, $b) = @_;
    return "$a/$b";
}

print exactly_two("alu", "ss"), "\n";
for my $args ([["alu"]], [["alu","ss","x"]]) {
    my @a = @{ $args->[0] };
    my $r = eval { exactly_two(@a) };
    print defined $r ? "got $r\n" : "rejected: $@";
}
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
alu/ss
rejected: exactly_two needs 2 arguments, got 1
rejected: exactly_two needs 2 arguments, got 3

Both wrong calls were rejected, and the message names the count. That is the difference between this and Chapter 10.2 §4's unchecked version, where the three-argument call silently discarded the extra one and returned a plausible answer.

Which level to use depends on who calls the subroutine:

CheckWrittenUse when
none—a private helper in a 50-line script
required-onlydie ... unless defined $block;most subroutines
exact countdie ... unless @_ == 2;something other scripts call
signaturesub f ($a, $b) { }you can accept the experimental warning

Required-only is the right default. It catches the mistake that actually happens — a caller omitting a mandatory argument — without rejecting a future caller who passes an extra one for a parameter you have not added yet.

The exact-count check is worth adding to a subroutine with a stable published interface, precisely because it catches the too-many case that nothing else will. @_ == 2 compares the array in numeric context, which is its length — Chapter 5.7's rule.

Where you put that check interacts with §1's choice of unpacking form, and the combination that breaks is worth seeing:

Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — check-order.pl, the check placed AFTER the unpacking
  1 arg(s): list_after: needs 2, got 1
  1 arg(s): shift_after: needs 2, got 0
  2 arg(s): ok 1/2
  2 arg(s): shift_after: needs 2, got 0
  3 arg(s): list_after: needs 2, got 3
  3 arg(s): shift_after: needs 2, got 1

With list assignment the check works wherever you put it — the counts are right (1 and 3) and the correct call passes, because my ($a, $b) = @_; copies without consuming.

With shift the same check is wrong in every row. It rejects the correct two-argument call (got 0), because both shifts emptied @_ before the check ran, and it misreports the other two counts as 0 and 1.

So: a count check must come before any shift. With list assignment the position does not matter, which is one more reason §1 recommends it — there is one fewer ordering rule to remember.

Chapter 10.11 covers the signature form, which is the only one where Perl itself raises the error — measured there as a fatal Too few arguments for subroutine ... (got 1; expected 2).

4. Copy First — the Second Reason

There is one more argument for my ($a, $b) = @_; that has nothing to do with readability, and it is the stronger one.

Azvya Education Pvt. Ltd.VLSI Mentor
Measured in Chapter 10.4
sub clobber { $_[0] = "CHANGED" }

my $value = "original";
clobber($value);
# $value is now "CHANGED"

@_ aliases the caller's variables. Writing to $_[0] writes to whatever the caller passed, in the caller's scope. Copying into my variables on the first line makes that impossible by accident, because everything after that line operates on copies.

Chapter 10.4 is the whole chapter on it, including the cases where it is useful on purpose. For now the practical consequence is a rule:

Unpack on the first line, then never mention @_ again — except for a deliberate arity check, or a subroutine whose interface genuinely is a variable-length list.

5. Why This Matters in Engineering Work

The first line is the subroutine's documentation. my ($block, $corner, $limit) = @_; tells a reader the interface without a comment. Four shifts spread across four lines tell them the same thing more slowly, and a body that uses $_[0] and $_[2] tells them nothing at all.

||= on a numeric option is a real production bug. --retries 0, --limit 0 and --jobs 0 are all things people pass deliberately, and ||= ignores all three. The symptom is a tool that does not honour a setting, which gets attributed to the option parser rather than to a defaulting line three files away.

And a required-argument check pays for itself on the first refactor. Adding a parameter to a subroutine leaves every existing caller passing one argument short — Chapter 10.2 §6 measured that nothing complains. A defined check on the new parameter turns that into an immediate error naming the subroutine.

6. Common Mistakes

||= for a default. It overwrites a deliberate 0 or "". Use //=.

Several shift statements instead of one list assignment. Four lines where one does, and @_ is gone afterwards.

Using $_[0] throughout the body. It aliases the caller's variable — Chapter 10.4.

An optional parameter in the middle of a positional list. Callers must pass undef as a placeholder. Use named arguments.

Checking arity after shift. The shifts emptied @_, so the check rejects even a correct call — measured got 0 for two arguments.

No check on a required argument. A caller omitting it gets undef and a plausible wrong answer.

An exact-count check on a private helper. It rejects future callers for no benefit; check what is required instead.

7. Exercise

Exercise 1 — the two forms. Write the same two-parameter subroutine twice, once with list assignment and once with two shifts. Have each report scalar(@_) at the end. Record both.

Exercise 2 — order sensitivity. In the shift version, insert a print statement between the two shift lines. Then swap the two shift lines and record what the parameters now hold.

Exercise 3 — defaults. Write §2's report with //= and call it five ways: one argument, two, three, with an explicit undef in the middle, and with an explicit 0. Record all five.

Exercise 4 — the operator comparison. Write with_or and with_dor from §2's callout and call each with 0, 5, "" and nothing. Record the eight results and name the two rows where they disagree.

Exercise 5 — the realistic bug. Write a subroutine taking retries with ||= 3, call it with 0, and print how many retries it would perform. Then fix it with //= and confirm.

Exercise 6 — the exact-count check and where to put it. Write exactly_two with the check before the unpacking. Call it with one, two and three arguments, catching the errors with eval, and record all three. Then build two more versions with the check placed after the unpacking — one using list assignment, one using two shifts — and call each with one, two and three arguments. Record all six results and say which version rejects a correct call.

Exercise 7 — required-only. Write a three-parameter subroutine with one required argument and two optional ones. Call it with only the required one, then with none, and record both.

Exercise 8 — adding a parameter. Write a two-parameter subroutine and a caller. Add a third required parameter to the subroutine but not to the caller. Record what happens with no check, then add a defined check and record it again.

8. Summary

  • Name the parameters on the first line: my ($a, $b) = @_;.
  • List assignment copies and leaves @_ intact; shift consumes it — measured, @_ had 2 afterwards against 0.
  • shift is idiomatic for one thing only: my $self = shift; to take a method's invocant.
  • Default with //=, never ||=. Measured: a caller passing 0 got 10 from ||= and 0 from //=; the same for an empty string.
  • The falsy values are the ones callers pass on purpose — retries => 0, verbose => 0, prefix => "".
  • An explicit undef takes the default under //=, which is how a caller skips a middle parameter.
  • Order the list required-first, optional-after. An optional parameter in the middle means it is time for named arguments.
  • Check what is required with defined; add an exact-count check only for a published interface. It must come before any shift — measured, a check after two shifts rejects the correct two-argument call with got 0. After a list assignment the position is harmless.
  • Copying first also prevents writing through @_ to the caller's variables — Chapter 10.4.

Chapter 10.4 is that aliasing: why $_[0] is not a copy, what it lets you do on purpose, and what it does by accident.

Where this fits

Part of the Perl curriculum.