DEV Community

Nicholas Hubbard
Nicholas Hubbard

Posted on

Named Subroutine Arguments in Perl

Naming your subroutine arguments has benefits around increasing the readability of your code. Lets look at an example of code that can benefit from named arguments:

sub safe_open {

    my $file = shift @_;
    my $mode = shift @_;
    my $die_on_failure = shift @_;

    my $success = open(my $fh, $mode, $file);

    if ((not $success) and $die_on_failure) {
        die "error: failed to open '$file': $!\n";
    }

    return $fh;
}
Enter fullscreen mode Exit fullscreen mode

In the following examples we can see that the third argument allows us to control if we should kill the program if the file cannot be opened:

my $fh = safe_open('/file/that/doesnt/exist', '>', 1);
print $fh "Hello, World!\n";

# OUTPUT:
# error: failed to open '/file/that/doesnt/exist': No such file or directory
Enter fullscreen mode Exit fullscreen mode
my $fh = safe_open('/file/that/doesnt/exist', '>', 0);
print $fh "Hello, World!\n";

# OUTPUT:
# print() on closed filehandle $fh at ./scratch.pl line 22.
Enter fullscreen mode Exit fullscreen mode

Imagine if somebody who has never worked with this code before comes across these calls to &safe_open. It will be impossible for them to know what the third argument stands for unless they go and look at the actual code of the subroutine.

To make it easier for people to understand the meaning of the arguments, we can name them!

sub safe_open {

    my %args = (
        FILE => '',
        MODE => '',
        DIE_ON_FAILURE => 1,
        @_
    );

    $args{FILE} or die "error: missing FILE arg in call to &safe_open\n";
    $args{MODE} or die "error: missing MODE arg in call to &safe_open\n";

    my $success = open(my $fh, $args{MODE}, $args{FILE});

    if ((not $success) and $args{DIE_ON_FAILURE}) {
        die "error: failed to open '$args{FILE}': $!\n";
    }

    return $fh;
}
Enter fullscreen mode Exit fullscreen mode

Lets look at some examples of calling this subroutine:

my $fh = safe_open(FILE => '/file/that/doesnt/exist', MODE => '>', DIE_ON_FAILURE => 1);
print $fh "Hello, World!\n";

# OUTPUT:
# error: failed to open '/file/that/doesnt/exist': No such file or directory
Enter fullscreen mode Exit fullscreen mode
my $fh = safe_open(FILE => '/file/that/doesnt/exist', MODE => '>');
print $fh "Hello, World!\n";

# OUTPUT:
# error: failed to open '/file/that/doesnt/exist': No such file or directory
Enter fullscreen mode Exit fullscreen mode
my $fh = safe_open(DIE_ON_FAILURE => 0, FILE => '/file/that/doesnt/exist', MODE => '>');
print $fh "Hello, World!\n";

# OUTPUT:
# print() on closed filehandle $fh at ./scratch.pl line 25.
Enter fullscreen mode Exit fullscreen mode

There are a few things to note from these examples.

  • The arguments must be explicitly named using ARG => value syntax.
  • The argument order does not matter.
  • The DIE_ON_FAILURE argument is optional and defaults to true.

Our &safe_open subroutine has these features because of the strategy used for constructing the %args hash.

The reason the argument order doesn't matter is because hashes are unordered and in the end we are constructing a hash out of the argument list.

An array can be used to construct a hash as long as it has an even number of elements. We leverage this to use the @_ array to construct the %args hash. Here is an example of this behavior:

my @array = ('foo', 12, 'bar', 13);

my %hash = @array;

print "\$hash{'foo'} = $hash{'foo'}\n";
print "\$hash{'bar'} = $hash{'bar'}\n";

# OUTPUT:
# $hash{'foo'} = 12
# $hash{'bar'} = 13
Enter fullscreen mode Exit fullscreen mode

We are able to give default argument values because of the fact that, if a hash definition defines the same key multiple times, then the last definition is used. If the user leaves off an argument, then it is not redefined and stays set to the default. Consider this example:

my %hash = (
    FOO => 12,
    FOO => 13
);

print "\$hash{'FOO'} = $hash{'FOO'}\n";

# OUTPUT:
# $hash{'FOO'} = 13
Enter fullscreen mode Exit fullscreen mode

Synopsis

We can use the subroutines argument array (@_) to construct a hash, which can give us significant benefits in terms of code readability. By constructing a hash from the argument array, all calls to the subroutine must explicitly name their arguments the argument order becomes irrelevant, and arguments can be given default values.

Top comments (0)