tags:

views:

80

answers:

3

I'm finding myself repeatedly writing and rewriting the following kind of code:

 my %default = (x => "a", y => "b");
 sub new
 {
      my ($package, $config) = @_;
      my $self = {%default};
      for my $k (keys %default) {
           $self->{$k} = $config->{$k} if defined $config->{$k};
      }
      for my $k (keys %$config) {
           if (! exists $default{$k}) {
                carp "Unknown config option $k\n";
           }
      }
      bless $self;
      # etc. etc.
 }

Before I make my own module to do this, I was just wondering if there is anything already on CPAN like that? I just want this very simple above functionality, so suggesting using Moose is not a suitable answer to this question.

+10  A: 

Moose supports defaults for attributes, e.g.:

has 'foo' => ( is => 'rw', isa => 'Int', default => 42 );

But if you don't want to go down the Moose route, a simpler way of achieving what you want is:

sub new { 
    my ( $package, %config ) = @_;

    my %defaults = ( x => 'a', y => 'b' );

    my $self = { %defaults, %config };

    # error checking here

    return bless $self, $package;
}

Since specifying the same hash key twice in a hash initialization will clobber the first one, any keys in %config will simply override those in %defaults.

friedo
Thanks for the tip with the doubled hash. I definitely need to check for stray stuff in %config too though.
Kinopiko
The doubled hash trick looks very nice and seems to work for me, but to my surprise perldata doesn't actually say what happens when a hash is assigned a list containing duplicate keys. Do you have any documentation for it?
j_random_hacker
@random, there's a brief mention of it in `perlfaq4`, see "How can I get the unique keys from two hashes?" at http://perldoc.perl.org/perlfaq4.html#How-can-I-get-the-unique-keys-from-two-hashes?
friedo
@friedo: Thanks. Perldata should say how this is handled, if I can figure out who maintains the docs I'll give them a patch.
j_random_hacker
+2  A: 

If you're already using Moose in your modules, you can get this behaviour by combining MooseX::Getopt and MooseX::SimpleConfig. Your config file can contain the defaults, and then you override anything as needed by passing those values to the constructor:

my $obj = Class->new_with_options(configfile => "myconfig.yaml", key1 => 'val', key2 => 'val');

package Class;
use Moose;
with 'MooseX::Getopt::Strict',
    'MooseX::SimpleConfig';

has configfile => (
    is => 'ro', isa => 'Str',
    traits => ['Getopt'],
    documentation => 'File containing default configs',
    lazy => 1,
    default => sub { File::Spec->catfile($base_dir, 'my_default_config.yaml') },
);

has [ qw(key1 key2) ] => (
    is => 'ro', isa => 'Str',
);
Ether
+3  A: 

Params::Validate might be of some help. It would allow you to drop the %defaults hash and specify defaults for each (possibly optional) parameter.

In addition, you can make that a little less verbose by using map. Of course, this will silently ignore invalid arguments.

#!/usr/bin/perl

package My::Class;

use strict; use warnings;

my %defaults = ( x => 'a', y => 'b' );

sub new {
    my $class = shift;
    my ($args) = @_;

    my $self = {
        %defaults,
        map {
            exists $args->{$_} ? ($_ => $args->{$_}) : ()
        } keys %defaults,
    };

    return bless $self, $class;
}

package main;

use strict; use warnings;

my $x = My::Class->new({ x => 1, z => 10});

use YAML;
print Dump $x;

Output:

--- !!perl/hash:My::Class
x: 1
y: b
Sinan Ünür
OK I accept there isn't really an answer so accept for mentioning Params::Validate, which may be the closest thing to what I asked about.
Kinopiko
;-) - Thank you.
Sinan Ünür