Not satisfied with any of the Class:: modules as the basis for the 50+ module distro I'm working on, I rolled my own. It's too specific to be published independently, but here's the rundown...
The following routine, which simply verifies that label names exist in a defaults hash, is always available, though it is called almost exclusively by a single constructor which nearly all the classes inherit.
sub verify_args { my $defaults = shift; # leave the rest of @_ intact. # Verify that args came in pairs. croak "Expecting key => value pairs, but got odd number of args" if @_ % 2; # Verify keys, ignore values. while (@_) { my ($var, undef) = (shift, shift); croak "Invalid parameter: '$var'" unless exists $defaults->{$var}; } }
So... my example would be:
my %convert_defaults = ( from => 'here' to => 'eternity' thing => undef, ); sub convert { verify_args(\%convert_defaults, @_); my %args = (%convert_defaults, @_); ... }
For the small minority of performance-critical subs, such as constructors for frequently instantiated classes, I try to design things so that it's reasonably safe to skip verification -- for example requiring by very few arguments, or by using clone() against a template.
All subclasses of the base class KinoSearch::Util::Class inherit new() and init_instance() (among others).
Here's the important stuff from the base class:
package KinoSearch::Util::Class; use KinoSearch::Util::ToolSet; # strict, warnings, Carp, etc. use Clone 'clone'; use KinoSearch::Util::VerifyArgs qw( verify_args ); our %instance_vars = (); sub new { my $class = shift; # leave the rest of @_ intact. # Clone the instance_vars hash and bless it. $class = ref($class) || $class; my $defaults; { no strict 'refs'; $defaults = \%{ $class . '::instance_vars' }; } my $self = clone($defaults); bless $self, $class; # Verify argument labels. eval { verify_args( $defaults, @_ ) }; if ($@) { my ( $package, $filename, $line ) = caller; die "ERROR during attempt to create object of class '$class' \ +n" . " at $filename line $line:\n" . " $@\n"; } # Merge var => val pairs into object. %$self = ( %$self, @_ ); $self->init_instance; return $self; } sub init_instance { } # Class method to build the %instance_vars hash sub init_instance_vars { my $package = shift; no strict 'refs'; my $first_isa = ${ $package . '::ISA' }[0]; return ( %{ $first_isa . '::instance_vars' }, @_ ); } # use to define an abstract method sub abstract_death { my ( undef, $filename, $line, $methodname ) = caller(1); die "ERROR: $methodname', called at $filename line $line, is an " . "abstract method and must be defined in a subclass."; }
Here's the Analyzer abstract class which defines a single instance variable, language, and a single sub, analyze.
package KinoSearch::Analysis::Analyzer; use KinoSearch::Util::ToolSet; use base qw( KinoSearch::Util::Class ); our %instance_vars = __PACKAGE__->init_instance_vars( language => '', +); sub analyze { shift->abstract_death } 1;
... and here's the PolyAnalyzer class which inherits from it. new(), which is inherited from KinoSearch::Util::Class, verifies the label names. init_instance() does a little bit more, checking to make sure that either language or analyzers is defined.
package KinoSearch::Analysis::PolyAnalyzer; use KinoSearch::Util::ToolSet; use base qw( KinoSearch::Analysis::Analyzer ); use KinoSearch::Analysis::LCNormalizer; use KinoSearch::Analysis::Tokenizer; use KinoSearch::Analysis::Stemmer; our %instance_vars = __PACKAGE__->init_instance_vars( analyzers => und +ef, ); sub init_instance { my $self = shift; my $language = $self->{language} = lc( $self->{language} ); if ( !defined $self->{analyzers} ) { croak("Must specify either 'language' or 'analyzers'") unless $language; $self->{analyzers} = [ KinoSearch::Analysis::LCNormalizer->new( language => $lang +uage ), KinoSearch::Analysis::Tokenizer->new( language => $lang +uage ), KinoSearch::Analysis::Stemmer->new( language => $lang +uage ), ]; } } sub analyze { my ( $self, $field ) = @_; $_->analyze($field) for @{ $self->{analyzers} }; } 1;
The parameter verification in PolyAnalyzer's init_instance isn't that strong -- language isn't tested to see if it's a valid value, and analyzers isn't checked to verify that it's an arrayref -- but that's because I know that meaningful exceptions will be thrown somewhere down the line if invalid values are supplied.
The OO design is heavily influenced by Java, as the project is a loose port of Java Lucene and this scheme allows for a lot of parallel coding. However, the signature-based parameter verification in Lucene often leaves me scratching my head, e.g. as to what the "true" in fooThis(query, file, true) does. With signatures unavailable, I'm forced to use named parameters in Perl -- but in some ways I like 'em better anyway.
In reply to Re: Your named arguments
by creamygoodness
in thread Your named arguments
by Juerd
For: | Use: | ||
& | & | ||
< | < | ||
> | > | ||
[ | [ | ||
] | ] |