December 20, 2022·4 min read·engineeringperlsignaturesfunctions

Perl signature defaults can now distinguish missing and false arguments

Perl 5.37.7 adds defined-or and logical-or defaults to signature parameters. The choice determines whether zero, empty text, or an explicitly undefined argument survives parameter binding.

An optional argument has more possible states than present and absent. A caller can explicitly supply undef, supply a false value, or omit the argument entirely. Those choices do not always mean the same thing to a function. Perl 5.37.7 adds //= and ||= default expressions to subroutine signatures so a declaration can express which states trigger the default. The release notes document the addition, and the release history records December 20 as the release date.

The existing = form still defaults only when an argument is omitted. The new forms extend that rule to an explicitly undefined value or to any false value. This is a concrete interface decision that previously needed additional logic in the function body.

Defaulting is part of binding arguments

The subroutine manual included in this release describes a signature as executable parameter binding before the braced body. It checks the argument list and populates lexical variables. If the argument list violates the declaration, the signature throws an exception before body statements run.

An ordinary optional parameter uses =. Its default expression runs only when its corresponding argument was omitted. A supplied value does not trigger the default just because it is undefined or false.

use strict;
use warnings;
use feature 'signatures';

sub omission_only ($value = 10) {
    return defined($value) ? $value : "missing";
}

print omission_only(), "\n";
print omission_only(undef), "\n";

The first call returns the default number. The second retains the explicitly undefined argument, which the body represents with the word missing. Supplying an argument and supplying a useful value remain separate matters under this declaration.

I prefer that distinction to be deliberate in a small Perl function. Text-processing code often has legitimate empty values, so replacing every false value with a default can change data. Making the rule visible beside the parameter helps a reader establish the contract before inspecting its use.

Defined-or preserves other false values

A //= default applies when the argument is absent or when the supplied argument is undef. This is useful when an undefined argument means to use the normal setting, while a supplied zero or empty string carries meaning.

use strict;
use warnings;
use feature 'signatures';

sub quantity ($value //= 10) {
    return $value;
}

print quantity(), "\n";
print quantity(undef), "\n";
print quantity(0), "\n";

The first two calls use the default. The last retains zero. This lets a caller choose zero without it being mistaken for an instruction to select ten. It does not validate the value as an acceptable quantity, because signature defaulting and domain validation are different operations.

The manual permits a default expression to refer to earlier parameters. It evaluates default expressions at call time, so their values can depend on the current call. The expanded operators make the condition for that evaluation more expressive. A default with side effects now needs review against the selected condition, since explicit undef can cause evaluation under //= when ordinary = would not.

Logical-or chooses a broader fallback policy

The ||= form applies the default whenever the supplied value is false. The manual explicitly includes absence and undef in that case. Zero and empty text therefore do not survive this defaulting rule as they do under the defined-or form.

use strict;
use warnings;
use feature 'signatures';

sub nonzero_default ($value ||= 10) {
    return $value;
}

print nonzero_default(0), "\n";
print nonzero_default(3), "\n";

The first call uses ten, while the second retains three. The declaration states a fallback policy based on Perl truthiness. It should be chosen because the interface intentionally treats false inputs alike, not because the operator is a convenient replacement for checking definedness.

This distinction can be especially visible in a configuration interface. A zero value might mean disabling a limit, and an empty separator might mean concatenating text directly. Those are examples of possible application contracts, not behavior supplied automatically by signatures. The declaration needs to agree with the contract the function actually promises.

Optional parameters retain their ordering rules

The new operators do not remove the existing structure of signatures. Optional positional parameters follow mandatory ones. A slurpy array or hash comes last and cannot have a default, since an absent remainder already supplies an empty container. These rules let the signature determine how to distribute the caller's argument list before entering the body. When several optional positional parameters exist, supplied arguments fill them from left to right. A caller cannot omit an interior position merely by providing fewer arguments. Passing an explicit undefined value at that position can now request its default when the declaration uses //=, while still supplying a later positional argument.

The same manual explains that signature variables receive copies of arguments, while argument-array elements alias the caller's arguments. Selecting a default in a signature is consequently a local parameter-binding operation. It should not be mistaken for assigning through the caller's argument-array alias.

My preference is to use the narrowest fallback condition that matches the function's intended inputs. The December snapshot gives that choice a direct spelling for omitted-only, undefined, and false arguments. A reviewer can now read the default's condition and value together in the signature, including the important case where an explicit zero must remain zero.