Signatures#
A signature is a parenthesised parameter list that goes immediately after the sub name (and after any attributes), in place of — or in addition to — the body’s manual @_ unpacking. Signatures introduce lexical parameter variables, perform arity checking at call time, and support default values and slurpy parameters.
Signatures stabilised in Perl 5.36; in 5.42 they are a production feature available with one of:
use feature 'signatures'; # explicit feature import
use v5.36; # implicit (signatures + other 5.36 features)
PetaPerl supports the full 5.42 signature syntax.
Basic shape#
use feature 'signatures';
sub add ($x, $y) { # two mandatory positional parameters
return $x + $y;
}
add(2, 3); # 5
add(2); # error: Too few arguments
add(2, 3, 4); # error: Too many arguments
Each $name in the signature declares a my lexical that receives the corresponding argument. Arity is checked at the call: the body never runs if the count is wrong.
This is the most concrete improvement over the classic form:
# Classic — silently accepts wrong arity
sub add {
my ($x, $y) = @_;
return $x + $y;
}
add(2); # $y is undef; later arithmetic warns
add(2, 3, 4); # third arg silently ignored
Defaults#
sub greet ($name, $greeting = 'Hello') {
return "$greeting, $name!";
}
greet('Alice'); # "Hello, Alice!"
greet('Alice', 'Hi'); # "Hi, Alice!"
The default is an arbitrary expression evaluated each time the parameter is omitted (so state and per-call computation both work). It can reference earlier parameters:
sub log_event ($message, $level = 'INFO', $prefix = "[$level]") {
print "$prefix $message\n";
}
log_event('starting'); # [INFO] starting
log_event('failure', 'ERROR'); # [ERROR] failure
log_event('done', 'OK', '>>'); # >> done
A default = undef is exactly equivalent to ”no default“ for arity (the parameter is still optional) but documents the intent. Use //=-style guards inside the body for ”undef means use my fallback“ semantics.
Optional vs mandatory#
Parameters are mandatory by default. A parameter becomes optional by:
Giving it a default value (
$x = 42).Putting it after another optional parameter (transitive).
Being followed by a slurpy
@arror%hash.
You cannot have a mandatory parameter after an optional one (except via slurpy):
sub bad ($x = 1, $y) { ... } # error: Mandatory parameter follows optional
Slurpy parameters#
The last parameter may be a slurpy @list or %hash. It eats all remaining arguments:
sub join_all ($sep, @parts) {
return join $sep, @parts;
}
join_all(', ', 'a', 'b', 'c'); # "a, b, c"
sub configure ($name, %opts) {
$opts{verbose} //= 0;
...
}
configure('demo', verbose => 1, port => 8080);
The slurpy hash also fails if the remaining list has odd length:
configure('demo', 'verbose'); # error: Odd-sized list given
This is the single most useful pattern signatures unlock: a named-parameter API with one positional plus a %opts slurp.
Anonymous parameters#
A $ (or @, %) with no name is a positional placeholder:
sub second ($, $x) { return $x }
second('ignored', 'kept'); # "kept"
This is mildly useful when adapting a callback to an interface that supplies more arguments than you care about.
Combining defaults and slurpy#
sub render ($template, $cache = 1, %opt) {
...
}
render('greeting.tt'); # cache=1, %opt empty
render('greeting.tt', 0); # cache=0, %opt empty
render('greeting.tt', 1, locale => 'de'); # all three
render('greeting.tt', locale => 'de'); # ERROR: 'locale' becomes $cache
# then 'de' is the lone slurp arg
# (odd-length hash) — diagnose carefully
That last case is the gotcha: a positional optional parameter followed by a slurpy hash is ambiguous with named-arg-only calls. Either drop the optional positional and put everything in the hash, or always pass the optional positional explicitly.
Migration from @_ unpacking#
The mechanical translation:
# Before
sub make_user {
my ($name, $email, %extra) = @_;
...
}
# After
use feature 'signatures';
sub make_user ($name, $email, %extra) {
...
}
You get arity checking and named lexicals for free. The body is unchanged.
The places where you cannot mechanically translate:
The body uses the count of arguments dynamically (
if (@_ >= 3)) — signatures fix arity at parse time, so rework into an optional parameter with a default.The body modifies
$_[0]for the aliasing effect — keep the classic form, since signatures introduce copies, breaking the alias.The body uses
goto &otherand wants to forward the original@_—@_is still populated under signatures in 5.42, but this is implementation-defined and may change. Keep the classic form for tail-call forwarding.
Signatures and prototypes are orthogonal#
Signatures and prototypes solve different problems:
Mechanism | Affects | When checked | Useful for |
|---|---|---|---|
signature | the body’s view | runtime | arity, named params |
prototype | how the parser parses | compile-time | DSL look-alikes |
A sub can have both, with the prototype written as an attribute:
use feature 'signatures';
sub mygrep :prototype(&@) ($code, @list) {
grep { $code->($_) } @list;
}
mygrep { /foo/ } @items; # prototype lets us drop `sub`
# signature gives us $code and @list
The prototype shapes the call site; the signature shapes the body. They do not duplicate each other.
The single biggest reason to migrate#
Catching wrong-arity calls at the moment they happen — not several lines later when you finally do $y + 1 and trip a warning about an undef value. Signatures turn a class of subtle bugs into a clear error message.
See also#
Arguments and
@_— the classic mechanism that signatures coexist with.Prototypes — different mechanism, often confused with signatures.
sub— the keyword’s perlfunc page.Declaration — where signatures sit in the declaration syntax.
Scoping — signature parameters are
my-style lexicals.