Perl Part I · Module 10 · Subroutines and Arguments
Signatures and Prototypes
Everything so far in this module has unpacked arguments by hand, because that is what Perl required for most of its history and what almost all existing code does. Newer Perl lets you name the parameters in the definition instead, with defaults, and gives you the one thing hand-unpacking cannot: an error when the caller passes the wrong number of arguments. There is also an older feature whose syntax looks almost identical, which does something else entirely — it changes how calls to the subroutine are parsed, checks nothing about types, and is one of the more misunderstood corners of the language. Knowing which one you are looking at matters, because they appear in the same position and mean different things.
Intermediate15 min readPerlPart ISubroutines
Part I · Module 10 · Chapter 10.11 · Signatures and Prototypes
1. Signatures Name the Parameters
use strict;
use warnings;
use feature "signatures";
sub add ($a, $b) { return $a + $b }
printf "add(2, 3) = %d\n", add(2, 3);add(2, 3) = 5The signatures feature is experimental at sig-basic.pl line 5.0The parameters are named in the definition, there is no my ($a, $b) = @_; line, and it works — with a warning on every compile.
That warning is the headline fact about signatures on Perl 5.34, which is the version used throughout this module. The feature is usable and marked experimental, which has two practical consequences covered in §4.
Silencing the warning is per-category, and defaults work:
use strict;
use warnings;
use feature "signatures";
no warnings "experimental::signatures";
sub add ($a, $b = 10) { return $a + $b }
printf "add(2, 3) = %d\n", add(2, 3);
printf "add(2) = %d\n", add(2);add(2, 3) = 5
add(2) = 12$b = 10 is a default in the declaration — the equivalent of Chapter 10.3 §2's $b //= 10; but stated where a reader looks for the interface. A default may be any expression, and it is evaluated only when the argument is absent.
2. The One Thing @_ Cannot Do
3. Slurpy Parameters
A signature can end with an array or a hash, which collects whatever is left.
use strict;
use warnings;
use feature "signatures";
no warnings "experimental::signatures";
sub first_and_rest ($first, @rest) { return "$first + " . scalar(@rest) . " more" }
sub named_rest ($first, %opt) { return "$first with " . join(",", map { "$_=$opt{$_}" } sort keys %opt) }
print first_and_rest(1, 2, 3, 4), "\n";
print named_rest("alu", corner => "ss", limit => 5), "\n";1 + 3 more
alu with corner=ss,limit=5Two useful shapes:
($first, @rest)is the variadic form — one required argument and any number after it. The arity check applies only to the named parameters, so@restmay be empty.($first, %opt)makes Chapter 10.5's pattern declarative. The named arguments arrive as a hash without amy %args = @_;line, and the required positional argument is checked.
That second form is genuinely nicer than the hand-written version, with one caveat: the defaults still have to be applied in the body, because a signature default applies to a scalar parameter and not to keys inside a slurpy hash:
sub report ($block, %opt) {
$opt{corner} //= "ss";
$opt{limit} //= 0;
...
}So the signature buys the required-argument check and loses nothing; the defaults line is the same as Chapter 10.3's.
An odd number of named arguments behaves better than the hand-written version, which is worth measuring because it is easy to assume otherwise:
Odd name/value argument for subroutine 'main::r' at -e line 3.255That is a different message and a fatal error. Chapter 10.5 §4 measured the hand-written my %args = @_; form giving Odd number of elements in hash assignment as a warning, with the hash still built and exit 0 — and with the line number pointing inside the subroutine rather than at the caller.
The signature version names the subroutine, refuses to run, and exits non-zero. So for named arguments a slurpy-hash signature gives you two checks the hand-written form does not: the required positional argument, and a fatal odd-count error.
4. Should You Use Them?
An honest answer, because this is the chapter's real question.
What "experimental" means here. The warning is not a prediction that signatures will be removed — they have been in Perl since 5.20, were used widely before 5.36 made them stable, and the design has settled. What it means in practice is:
- Every compile prints a warning unless you add
no warnings "experimental::signatures", which is a line of noise in every file that uses them. - Details have changed between versions. The exact spelling of what is allowed in a signature has moved over the 5.20–5.36 range, so a signature written against one Perl may not behave identically on an older one.
- They require a
use featureline. A file using signatures will not compile at all on a Perl older than 5.20, which for an EDA environment running a vendor-supplied Perl is a real constraint.
The recommendation, and why the curriculum marks this chapter Medium:
| Situation | Use |
|---|---|
| a script only you run, on a Perl you control | signatures, with the no warnings line |
| a script that may run on an old vendor Perl | hand-unpacking |
| adding to an existing codebase | whatever that codebase already uses |
| a shared library with many callers | hand-unpacking plus an explicit count check |
The deciding question is the Perl version you cannot choose. Flow scripts in this trade frequently run under whatever Perl a tool ships with, and perl -v on a vendor installation is not always recent. Chapter 1.2 covers finding that out, and it is worth doing before committing a file to signatures.
What this means for reading: hand-unpacking is what you will mostly see, which is why Chapter 10.3 is Foundation and this chapter is Medium. Signatures are worth recognising and worth using where you can.
5. Prototypes Are Not Signatures
The syntax collides, and the meanings do not overlap at all.
use strict;
use warnings;
sub pr ($$) { return "@_" }
print "pr('a','b') = ", pr("a", "b"), "\n";
my $ok = eval q{ pr(1); 1 };
print $ok ? "pr(1) compiled\n" : "pr(1) failed to compile\n";pr('a','b') = a b
pr(1) failed to compilesub pr ($$) without use feature "signatures" is a prototype, not a parameter list. $$ does not mean "two scalars called $ and $" — it means "this subroutine takes exactly two scalar-context arguments", and Perl enforces that when it compiles each call.
The clearest demonstration is passing an array:
Not enough arguments for main::p at -e line 1, near "@a)"
Execution of -e aborted due to compilation errors.255A two-element array is not two arguments to a prototyped subroutine. The prototype imposed scalar context on each expected argument, so @a counted as one, and the whole program failed to compile.
That is the key difference, and it has three parts:
| Signature | Prototype | |
|---|---|---|
needs use feature | yes | no |
| names parameters | yes | no — $$ names nothing |
| checks the count | at run time, fatally | at compile time |
| changes how calls parse | no | yes — imposes context |
| checks types | no | no |
@_ still populated | yes | yes |
Neither checks types. The $ in a prototype means "scalar context", not "must be a number" or "must be a string". A prototype of ($$) accepts pr("a", {}) happily.
What prototypes exist for is making a subroutine parse like a builtin:
sub mymax (\@) { my ($aref) = @_; ... } # takes an array, like push does
mymax(@values); # no backslash needed at the call siteThat \@ prototype makes Perl pass a reference automatically, which is how push can take @array as its first argument without a \. It is a genuine feature and a rare need.
6. Why This Matters in Engineering Work
The arity check is the one real gap signatures fill. Chapter 10.2 §4 measured that an extra argument is silently discarded, which is the most invisible of the argument mistakes. A signature turns it into a message naming both counts — and more importantly, turns "I added a parameter" from a silent behaviour change into a list of call sites to fix.
The Perl version is often not yours to choose. A script that must run under a tool's bundled Perl cannot use use feature "signatures" if that Perl predates 5.20, and finding out is one perl -v away. That constraint, not the language design, is why hand-unpacking remains the default in this trade.
And a prototype found in inherited code is worth reading carefully. It changes how calls parse, is bypassed by & and by code references, and applies only below the declaration. A subroutine whose behaviour depends on how it was called is hard to reason about, and a prototype is the one feature in this module that creates that situation on purpose.
7. Common Mistakes
Reading sub f ($$) as a parameter list. Without use feature "signatures" it is a prototype and names nothing.
Expecting a prototype to check types. Neither feature does. $ means scalar context.
Passing an array to a ($$) prototype. It counts as one argument and the program fails to compile.
Expecting a signature's arity error at compile time. It is a runtime error, catchable with eval.
Forgetting no warnings "experimental::signatures" on 5.34. Every compile warns.
Using signatures in a script that must run on a vendor Perl. Check perl -v first; below 5.20 the file will not compile.
Expecting a signature default to apply to slurpy hash keys. Defaults apply to scalar parameters; key defaults still go in the body. (The odd-count check, though, is fatal rather than a warning.)
Adding a prototype to an ordinary subroutine. It is for emulating builtins, is invisible at the call site, and is bypassed by & and by code references.
8. Exercise
Exercise 1 — a signature. Write sub add ($a, $b) { ... } with use feature "signatures". Record STDOUT, STDERR and the exit status. Then add the no warnings line and record again.
Exercise 2 — a default. Give $b a default of 10 and call the subroutine with two arguments and with one. Record both. Then make the default an expression that prints something, and confirm it is evaluated only when the argument is absent.
Exercise 3 — the arity check. Write a two-parameter signature and call it with one, two and three arguments, catching failures with eval. Record all three messages. Then write the same subroutine with my ($a,$b) = @_; and repeat, recording what happens for each.
Exercise 4 — adding a parameter. Write a two-parameter subroutine with a signature and a caller passing two arguments. Add a third required parameter without touching the caller. Record what happens. Then do the same with hand-unpacking.
Exercise 5 — slurpy. Write ($first, @rest) and ($first, %opt) signatures and call each. Then call the %opt one with an odd number of trailing arguments and record the exact message and exit status. Compare both against the hand-written my %args = @_; version from Chapter 10.5 §4.
Exercise 6 — the version question. Run perl -v and record the version. Say whether a signature-using file would compile there, and what you would check before committing one to a shared flow.
Exercise 7 — a prototype. Write sub pr ($$) { return "@_" } with no use feature line. Call it with two scalars, then with one, then with a two-element array. Record all three outcomes and note which failed at compile time.
Exercise 8 — the bypass. Call that prototyped subroutine three ways with a single argument: directly, as &pr(1), and through a code reference. Record which of the three the prototype rejected.
9. Summary
- Signatures name parameters in the definition and support defaults:
sub add ($a, $b = 10). - On Perl 5.34 they work and warn —
The signatures feature is experimentalon every compile, silenced withno warnings "experimental::signatures". - They enforce the argument count, fatally — measured
Too few arguments for subroutine 'main::two' (got 1; expected 2)and the matching too-many message. That is the one thing@_cannot do. - So adding a parameter breaks existing callers immediately instead of leaving them passing
undef. - The arity error is at run time, catchable with
eval— a signature is a guaranteed check, not a static one. - Slurpy
@restand%optwork, and($first, %opt)makes Chapter 10.5's pattern declarative — key defaults still go in the body, but an odd name/value count is fatal (Odd name/value argument for subroutine, exit 255) where the hand-written form only warns and continues. - The deciding question is the Perl version you cannot choose. Below 5.20 a signature file will not compile at all, which is why hand-unpacking remains the default in this trade.
- A prototype is a completely different feature.
sub pr ($$)withoutuse featurenames nothing, checks shape at compile time, and imposes context — measured, a two-element array counted as one argument and the program failed to compile. - Neither feature checks types.
- Prototypes are bypassed by
&and by code references, and apply only to calls below the declaration. Use them only to emulate a builtin.
Chapter 10.12 closes the module with the three decisions that make a subroutine worth calling from a second script: what it takes, what it returns, and what it does when the input is wrong.
Where this fits
Part of the Perl curriculum.
