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 @arr or %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 &other and 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.