Reputation: 4002
In Perl, if I want to use named parameters in an object constructor, my code seems a bit clumsy if I wish to have some validation.
sub new {
my $class = shift;
my $self = {};
my %args = @_;
foreach my $argname (keys %args) {
if ($argname eq 'FOO') { $self->{$argname} = $args{$argname}; }
elsif ($argname eq 'BAR') { $self->{$argname} = $args{$argname}; }
elsif ($argname eq 'BAZ') { $self->{$argname} = $args{$argname}; }
…
else { die "illegal argument $argname\n"; }
}
bless $self;
return $self;
}
Firstly it seems a bit clumsy to have a temporary hash (%args
). Secondly the whole if
chain seems verbose and tedious.
The latter can be simplified to
if ('-FOO-BAR-BAZ-'=~m/-$argname-/) { $self->{$argname} = $args{$argname} }
else { die "..."; }
but I imagine this can be improved.
If I need to check values, the if … elsif
chain is still necessary?
I've searched a little but cannot find a better idiom. Is there one (other than using a Perl OO framework of some sort)
Upvotes: 1
Views: 633
Reputation: 4002
For completeness I'm adding this answer (to my own question) describing what I'm actually going to do, which is based on elements from several answers.
sub new {
my $package = shift;
# 1. validate argument names
my %args = @_;
my $valid = '^FOO|BAR|BAZ$';
for (keys %args) { die "invalid arg $_\n" unless /$valid/; }
# 2. construct instance from arguments
return bless { @_ };
}
I've accepted Sebastian's answer although I'm not using Params::Validate yet.
Notes:
I'm deploying to a server that has Perl 5.8 (really) but not Params::Validate. I have reasons for not yet pushing for the upgrades to 5.10.x etc.
For my specific circumstance the above strikes a good balance between brevity and readability. I can later add more validation without too much refactoring.
This compensates for one of the advantages of a getter/setter or accessor style methods for setting parameters (compiler catches typos in parameter name as that is the method name) whilst being more concise.
For other people the above will not apply, so I have accepted Sebastian's answer which I feel is the best one in general (YMMV).
Upvotes: 0
Reputation: 2791
I found myself constantly writing unnecessary code which checked the given parameters. But then I discovered Params::Validate. It is easy to use and if the validation fails it provides very clear and user-friendly error messages. Covering all possible combinations of parameters and their error messages is a tedious task. I prefer this way instead:
use Params::Validate qw/:all/;
sub new {
my $pkg = shift;
validate(
@_, {
foo => { type => SCALAR | ARRAYREF },
bar => { type => SCALAR, optional => 1},
baz => { type => ARRAYREF, default => ['value'] },
quux => { isa => 'CGI' }
}
);
return bless { @_ }, $pkg;
}
And later this code
MyApp::Something->new(
foo => 123,
bbr => 'typo',
quux => CGI->new()
);
becomes:
The following parameter was passed in the call to MyApp::Something::new but was not listed in the validation options: bbr
at test.pl line 14.
MyApp::Something::new(undef, 'foo', 123, 'bbr', 'typo', 'quux', 'CGI=HASH(0x7fd4fa1857e0)') called at test.pl line 27
Upvotes: 8
Reputation: 3037
Warning! Untested code.
Check for valid keys.
die "invalid args" if grep { ! /^FOO|BAR|BAZ$/ } keys %args;
Store %args
.
$self->{$_} = $args{$_} foreach(keys %args);
Upvotes: 1
Reputation: 67908
You can use smart matching
my @validkeys = qw(FOO BAR BAZ);
if ($argname ~~ @validkeys) { # smart matching
$self->{$argname} = $args{$argname};
} else { die ... }
If you don't like the obscurity of the smart match operator you can swing together a regex
my $rx = '^' . join("|", @validkeys) . '$';
if ($argname =~ /$rx/) { ...
Upvotes: 2
Reputation: 3289
for validation you can define a hash of all legal argument and then just test, if the keys are in it or not
for example:
my %legal = ('FOO' => 1, 'BAR' => 1, 'BAZ' => 1);
my %args = @_;
foreach my $argname (keys %args) {
if(exists $legal{$argname}) { $self->{$argname} = $args{$argname}; }
else { die "illegal argument $argname\n"; }
}
about the clumsyness: well that's the to do it in perl it can use hashes efficiently and the hash literals are readable
Upvotes: 1