Archive for the ‘Best Practices’ Category

Parameter hash patterns in Perl 5

2010-03-08

It’s a common pattern in Perl 5 to use a hash for a subroutine’s arguments, or some of them. Damian Conway explains this pattern in his excellent Perl Best Practices. I’ll first briefly recap the standard forms, then show how you can support both standard arguments and a hash for extra arguments.

The basic form looks like this:

pad({ text=>$line, cols=>20 }) 

You can actually leave out the curly hash-braces and just pass a list of key-value pairs:

pad( text=>$line, cols=>20 ) 

That’s what you often see in practice, but Conway argues against doing that. It allows mismatches such as passing cols=>20..21 (two values on the right hand side) to pass compilation.

Most of the time that won’t be a problem in practice, as the values of the pairs will be simple enough. But it’s better to do things in a uniform way that works in all situations, and the sub’s implementation depends on the way of passing the hash.

When passing an explicit hash enclosed in {}, you get it as a reference:

my ($hashref) = @_;
my $foo = $hash->{foo};

Using raw key-value pairs, you directly get a hash:

my %hash = @_;
my $foo = $hash{foo};

Obviously, the latter form does not allow to pass any arguments other than the hash. One more argument against doing that. I often write subs that take the necessary arguments directly, and optional ones, or “configuration” parameters, in a hash that may or may not be passed:

$uniprot->retrieve(@ids, {format=>'rdf', include=>1}): 

You can implement once and re-use a routine, say _get_args_and_conf, that handles this distinction between arguments and configuration so that your subs don’t have to. It looks at the arguments, checks if the last one is a hash, and if that’s the case, merges it with the default configuration and returns the arguments and the configuration separately. You would use it like that in your code:

my %RETRIEVE_DEFAULTS = (
    format => 'fasta',
    debug => 0 );

sub retrieve {
    my ($ids_ref, $conf_ref) =
        _get_args_and_conf(\%RETRIEVE_DEFAULTS, @_);
    # $ids_ref now contains the arguments, here some ids to
    # retrieve from uniprot.org, and $conf_ref contains the
    # configuration hash with the user's values if given, and the
    # default ones otherwise.
}

My implementation looks like that. The meat of the routine, the hash handling, is straight from Conway’s Best Practices.

sub _get_args_and_conf {
    my $default_conf_ref = shift;
    my @args = @_;
    croak "I need at least one argument!" if @_ < 1;

    # if last arg is a hash, it's additional configuration
    my %defaults = %{$default_conf_ref};
    my %conf = ref $args[-1] eq 'HASH' ?
        (%defaults, %{pop @args}) : %defaults;
    if (@args < 1) {
        croak "I need at least one argument in addition to the hash!";
    }

    # TODO Deal with the case that the argument list is given as a
    # reference.

    return (\@args, \%conf);
}

Follow

Get every new post delivered to your Inbox.