perl subroutine reference - perl

I have a set of fields with each field having different set of validation rules.
I have placed the subroutine reference for validating a hash-ref.
Currently its in my constructor, but I want to take it out of my constructor in a private sub.
I have done it as below
sub new {
my $class = shift;
my $self = {#_};
$class = (ref($class)) ? ref $class : $class;
bless($self, $class);
$self->{Validations} = {
Field1 => {name => sub{$self->checkField1(#_);},args => [qw(a b c)]}
Field2 => {name => sub{$self->checkField2(#_);},args => {key1, val1}}
..
..
..
..
};
return $self;
}
Now I want to take out all this validation rules out of my constructor and want to do some thing like below, so that I have some better control over my validation rules based on types fields.(Say some rules are common in one set of fields and I can overwrite rules for other rules just by overwriting the values of fields.)
bless($self, $class);
$self->{Validations} = $self->_getValidation($self->{type});
return $self;
}
sub _getValidation{
my ($self,$type) = #_;
my $validation = {
Field1 => {name => sub {$self->checkField1(#_);}, args => {key1 => val1}},};
return $validation;
}
But I am getting Can't use string ("") as a subroutine ref while "strict refs" in use at... Can anybody tell me why is this behavior with sub ref. If I check my name key, its coming to be null or sub {DUMMY};

It looks to me like you are getting close to reinventing Moose poorly. Consider using Moose instead of building something similar, but less useful.
The error message means that you are passing in a string in a place where your code expects a code reference. Get a stack trace to figure out where the error is coming from.
You can do this by using Carp::Always, overriding the $SIG{__DIE__} handler to generate a stack trace, or inserting a Carp::confess into your code.
Here's a sigdie solution, stick this in your code where it will run before your module initialization:
$SIG{__DIE__} = sub { Carp::confess(#_) };
You may need to put it in a BEGIN block.
I'd really like to discourage you from taking this approach to building objects. You happily bless any random crap passed in to the constructor as part of your object! You blithely reach into your object internals. Field validation rules *do not belong in the constructor--they belong in the attribute mutators.
If you must use a DIY object, clean up your practices:
# Here's a bunch of validators.
# I set them up so that each attribute supports:
# Multiple validators per attribute
# Distinct error message per attribute
my %VALIDATORS = (
some_attribute => [
[ sub { 'foo1' }, 'Foo 1 is bad thing' ],
[ sub { 'foo2' }, 'Foo 2 is bad thing' ],
[ sub { 'foo3' }, 'Foo 3 is bad thing' ],
],
other_attribute => [ [ sub { 'bar' }, 'Bar is bad thing' ] ],
);
sub new {
my $class = shift; # Get the invocant
my %args = #_; # Get named arguments
# Do NOT make this a clone method as well
my $self = {};
bless $class, $self;
# Initialize the object;
for my $arg ( keys %args ) {
# Make sure we have a sane error message on a bad argument.
croak "Bogus argument $arg not allowed in $class\n"
unless $class->can( $arg );
$self->$arg( $args{$arg} );
}
return $self;
}
# Here's an example getter/setter method combined in one.
# You may prefer to separate get and set behavior.
sub some_attribute {
my $self = shift;
if( #_ ){
my $val = shift;
# Do any validation for the field
$_->[0]->($val) or croak $_->[1]
for #{ $VALIDATORS{some_attribute} || [] };
$self->{some_attribute} = $val;
}
return $self->{some_attribute};
}
All this code is very nice, but you have to repeat your attribute code for every attribute. This means a lot of error-prone boilerplate code. You can get around this issue by learning to use closures or string eval to dynamically create your methods, or you can use one of Perl's many class generation libraries such as Class::Accessor, Class::Struct, Accessor::Tiny and so forth.
Or you can learn [Moose][3]. Moose is the new(ish) object library that has been taking over Perl OOP practice. It provides a powerful set of features and dramatically reduces boilerplate over classical Perl OOP:
use Moose;
type 'Foo'
=> as 'Int'
=> where {
$_ > 23 and $_ < 42
}
=> message 'Monkeys flew out my butt';
has 'some_attribute' => (
is => 'rw',
isa => 'Foo',
);

I haven't read everything you had, but this struck me:
sub new {
my $class = shift;
my $self = {#_};
$class = (ref($class)) ? ref $class : $class;
bless($self, $class);
Normally, when you create a new object, the user doesn't pass $self as one of the objects. That's what you're creating.
You usually see something like this:
sub new {
my $class = shift; #Contains the class
my %params = #_; #What other parameters used
my $self = {}; #You're creating the $self object as a reference to something
foreach my $param (keys (%params)) {
$self->{$param} = $params{$param};
}
bless ($self, $class) #Class is provided. You don't have to check for it.
return $self #This is the object you created.
}
Now, $self doesn't have to be a reference to a hash as in the above example. It could be a reference to an array. Or maybe to a function. But, it's usually a reference. The main point, is that the user doesn't pass in $self because that's getting created by your new subroutine.
Nor, do you have to check the value of $class since that's given when the new subroutine is called.
If you want to do your verification in a private class (an excellent idea, by the way), you can do so after the bless:
sub new {
my $class = shift; #Contains the class
my %params = #_; #What other parameters used
my $self = {}; #You're creating the $self object as a reference to something
foreach my $param (keys (%params)) {
$self->{$param} = $params{$param};
}
bless ($self, $class) #Class is provided. You don't have to check for it.
#Now you can run your verifications since you've blessed the object created
if (not $self->_validate_parameters()) {
croak qq(Invalid parameters passed in class $class);
}
return $self #This is the object you created.
}

Related

Hiding a tie call from the user in Perl

How can I hide a "tie" call from the user so calling an accessor will implicitly do it for them?
I want to do this, because I have a data structure that can be accessed by the user, but values stored in this structure can be modified without the user's knowledge.
If an attribute in the data structure changes, I want any variables referencing that attribute modified as well so the user will always be using fresh data. Since the user will always want fresh data, it's simpler and more intuitive if the user doesn't even need to know it's happening.
This is what I have so far... it doesn't seem to work though, the output is:
hello
hello
What I want is:
hello
goodbye
Code:
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
{
package File;
use Moose;
has '_text' => (is => 'rw', isa => 'Str', required => 1);
sub text {
my ($self) = #_;
tie my $text, 'FileText', $self;
return $text;
}
}
{
package FileText;
use Tie::Scalar;
sub TIESCALAR {
my ($class, $obj) = #_;
return bless \$obj, $class;
}
sub FETCH {
my ($self) = #_;
return $$self->_text();
}
sub STORE {
die "READ ONLY";
}
}
my $file = 'File'->new('_text' => 'hello');
my $text = $file->text();
say $text;
$file->_text('goodbye');
say $text;
I would not recommend doing this. You're introducing "action at a distance" which leads to some very difficult to catch bugs. The user thinks they're getting a string. A lexical string can only be altered by changing it directly and obviously. It has to be altered in place or obviously passed into a function or a reference attached to something.
my $text = $file->text;
say $text; # let's say it's 'foo'
...do some stuff...
$file->text('bar');
...do some more stuff...
# I should be able to safely assume it will still be 'foo'
say $text;
That block of code is easy to understand because all the things which could affect $text are immediately visible. This is what lexical context is all about, isolating what can change a variable.
By returning a thing which can change at any time, you've quietly broken this assumption. There's no indication to the user that assumption has been broken. When they go to print $text and get bar it is non-obvious what changed $text. Anything in the whole program could change $text. That small block of code is now infinitely more complicated.
Another way to look at it is this: scalar variables in Perl have a defined interface. Part of that interface says how they can be changed. You are breaking this interface and lying to the user. This is how overloaded/tied variables are typically abused.
Whatever problem you're trying to solve, you're solving it by adding more problems, by making the code more complex and difficult to understand. I would step back and ask what problem you're trying to solve with tying.
What I would do instead is to just return a scalar reference. This alerts the user that it can be changed out from under them at any time. No magic to cover up a very important piece of information.
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
{
package File;
use Moose;
has 'text_ref' => (
is => 'rw',
isa => 'Ref',
default => sub {
return \("");
}
);
sub BUILDARGS {
my $class = shift;
my %args = #_;
# "Cast" a scalar to a scalar ref.
if( defined $args{text} ) {
$args{text_ref} = \(delete $args{text});
}
return \%args;
}
sub text {
my $self = shift;
if( #_ ) {
# Change the existing text object.
${$self->text_ref} = shift;
return;
}
else {
return $self->text_ref;
}
}
}
my $file = 'File'->new('text' => 'hello');
my $text = $file->text();
say $$text;
$file->text('goodbye');
say $$text;
That said, here's how you do what you want.
I would recommend against using tie. It is very slow, considerably slower than a method call, buggy and quirky. One of its quirks is that the tied nature is attached to the variable itself, not the referenced data. That means you can't return a tied variable.
Instead, I would recommend using an overloaded object to store your changing text.
{
package ChangingText;
# Moose wants class types to be in a .pm file. We have to explciitly
# tell it this is a class type.
use Moose::Util::TypeConstraints qw(class_type);
class_type('ChangingText');
use overload
'""' => sub {
my $self = shift;
return $$self;
},
fallback => 1;
sub new {
my $class = shift;
my $text = shift;
return bless \$text, $class;
}
sub set_text {
my $self = shift;
my $new_text = shift;
$$self = $new_text;
return;
}
}
Overloaded objects have their own caveats, mostly due to code which expects strings writing things like if !ref $arg, but they are easier to deal with than the deep tie bugs.
To make this transparent, store the ChangingText object in the File object and then put a hand made text accessor around it to handle plain strings. The accessor makes sure to reuse the same ChangingText object.
To complete the illusion, BUILDARGS is used to change plain text initialization arguments into a ChangingText object.
{
package File;
use Moose;
has 'text_obj' => (
is => 'rw',
isa => 'ChangingText',
default => sub {
return ChangingText->new;
}
);
sub BUILDARGS {
my $class = shift;
my %args = #_;
# "Cast" plain text into a text object
if( defined $args{text} ) {
$args{text_obj} = ChangingText->new(delete $args{text});
}
return \%args;
}
sub text {
my $self = shift;
if( #_ ) {
# Change the existing text object.
$self->text_obj->set_text(shift);
return;
}
else {
return $self->text_obj;
}
}
}
Then it works transparently.
my $file = File->new('text' => 'hello');
my $text = $file->text();
say $text; # hello
$file->text('goodbye');
say $text; # goodbye
return $text just returns the value of the variable, not the variable itself. You can return a reference to it, though:
sub text {
my ($self) = #_;
tie my $text, 'FileText', $self;
return \$text;
}
You then have to use $$text to dereference it:
my $file = 'File'->new('_text' => 'hello');
my $text = $file->text();
say $$text;
$file->_text('goodbye');
say $$text;

Unable to return the default value for the object attribute in perl

I have a module , when I am trying to get the default attribute set in initialization, is giving the following error when subroutine is being called get_name
Use of uninitialized value
sample code
package test;
#....
#....
sub new {
my ($class) = #_;
my $self = {};
bless $self,$class;
$self->_initialize();
return $self;
}
sub _initailalize {
my($self) = #_;
$self = {
_name => 'NA'
};
}
sub get_name {
return $_[0]->{_name};
}
valuable inputs required.
You are redefining $self in the local scope of _initialize and assigning a new hashref. That way you are not adding the _name key to the blessed object. After _initialize is done, your newly assigned $self (with a plain hashref) is gone. Thus in get_name it cannot access the key _name.
sub _initailalize { # btw typo here
my ($self) = #_;
$self->{_name} = 'NA';
}
If you do it like this, you will need to assign each member on an individual line.
As a suggestion, this built-in style of OO is very tedious. Try looking at Moose or it's derivatives as they are pretty powerful.

Perl array attribute inside an object

Tried to write a perl module with OOP, but it can add an object to an array, when I use Dump method, it will output wrong data like this. Where is my error ?
Thanks
bless( {
'_name' => 'CUSIP',
'_validation_array' => [],
'_seq' => '1'
}, 'Field' );
source code:
package Field;
sub new {
my $class = shift;
my $self = {
_name => shift,
_seq => shift,
_validation_array => [ #_ ],
};
bless($self, $class);
return $self;
};
sub pushValidation(){
my $validation = shift;
push(#{$self->{_validation_array}}, $validation);
};
sub dump(){
foreach my $validation (#{$self->{_validation_array} }) {
#print Dumper($validation);#will work,
print $validation->{name}; #error, Use of uninitialized value
}
}
1;
This is the way I call this method :
my $validationObj = new Validation($validation->{name}, $validation->{seq});
$field->pushValidation($validationObj);
I see several problems here, but the most serious one is here:
sub pushValidation() {
my $validation = shift;
push(#{$self->{_validation_array}}, $validation);
};
This function is expecting a $self argument, but isn't shifting it from the arguments. You need to add use strict; at the top of your Perl file. If it had been enabled, the issue would have been immediately obvious:
Global symbol "$self" requires explicit package name at <filename> line <line>.
Same thing goes for the dump() function. (By the way, dump is a bad method name, as there is an (obscure) Perl builtin function with the same name. But that's not a huge issue.)

How to create (or not) class instance methods at construction time based on inputs?

How would I create my class such that some methods will exist in the instance only if certain values were passed to the constructor?
Perhaps a more generic way of asking is: How can I add a method to an existing class instance?
You can attach an anonymous sub to an object based on flags:
use strict;
use warnings;
package Object;
sub new {
my $class = shift;
my $self = bless {}, $class;
my %args = #_;
if ($args{method}) {
$self->{method} = sub { print "hello\n" }
}
return $self;
}
sub method {
my $self = shift;
if (not defined $self->{method}) {
warn "Not bound\n";
return;
}
$self->{method}->();
}
1;
to use:
use Object;
my $obj1 = Object->new(method=>1);
$obj1->method();
my $obj2 = Object->new();
$obj2->method();
You can extend this to a number of methods through the same interface.
You can use Moose to apply a role at runtime.
package My::Class;
use Moose;
has foo => ( isa => 'Str', is => 'ro', required => 1 );
sub BUILD {
my $self = shift;
if ($self->foo eq 'bar') {
My::Class::Role->meta->apply($self);
}
}
no Moose;
package My::Class::Role;
use Moose::Role;
sub frobnicate {
my $self = shift;
print "Frobnicated!\n";
}
no Moose;
my $something = My::Class->new( foo => 'bar' );
print $something, "\n";
$something->frobnicate;
my $something_else = My::Class->new( foo => 'baz' );
print $something_else, "\n";
$something_else->frobnicate;
Gives:
Moose::Meta::Class::__ANON__::SERIAL::1=HASH(0x2fd5a10)
Frobnicated!
My::Class=HASH(0x2fd2c08)
Can't locate object method "frobnicate" via package "My::Class" at testmoose.pl line 32.
use AUTOLOAD to define the function. As a example method foo is called if $self->{foo} exists
sub AUTOLOAD {
my $methodname = $AUTOLOAD;
if ($methodname eq "foo" && exists($_[0]->{foo})){
goto &fooimplementationsub;
}
return;
}
An alternative technique is to use globs to define a new method at runtime
*PACKAGE::method = sub {
#code here
};
This has the disadvantage that the method is now visible to all instances of the class so is not quite what you want.
A third and possibly more risky/inefficient method is to use string eval
eval <<EOF
sub foo {
#code here
};
EOF
Again this has the disadvantage that the method is now visible to all instances of the class so is not quite what you want.
Methods are just subroutines in a package, and a package is just a hash holding typeglobs. And hashes can be modified at runtime.
So you could, in theory, add or remove methods given values in a constructor.
package WeirdClass;
sub new {
my ($class, $name, $code) = #_;
if ($name) {
no strict;
*{__PACKAGE__ . "::$name"} = $code;
}
bless {} => $class;
}
And then use it like:
my $object = WeirdClass->new(foo => sub {say "foo"});
$object->foo(); # prints "foo\n";
However, this method is available for all objects of that class:
my $another_object = WeirdClass->new();
$another_object->foo; # works too.
Using autoload, one can mock arbitrary methods:
package BetterClass;
sub new {
my ($class, %args) = #_;
bless \%args => $class;
}
# destructor will be called at cleanup, catch with empty implementation
sub DESTROY {};
sub AUTOLOAD {
my $self = shift;
(my $method = our $AUTOLOAD) =~ s/.*://; # $AUTOLOAD is like "BetterClass::foo"
# check if method is allowed
die "forbidden method $method" unless $self->{can}{$method};
# mock implementations
given ($method) {
say "foo" when "foo";
say "bar" when "bar";
when ("add") {
my ($x, $y) = #_;
return $x + $y;
}
default { die "unknown method $method" }
}
}
Then:
my $o = BetterClass->new(can => { foo => 1, bar => 0});
$o->foo;
my $p = BetterClass->new(can => {bar => 1, add => 1});
$p->bar;
say $p->add(5, 6);
Of course, these techniques can be combined freely.
Edit: can()
To make the AUTOLOAD work with can, the protected methods should be moved into a data structure:
my %methods;
BEGIN {
%methods = (
foo => sub {say "foo"},
bar => sub {say "bar"},
add => sub {
my ($self, $x, $y) = #_;
$x + $y;
},
);
}
Then override the can method:
# save a reference to the origional `can` before we override
my $orig_can;
BEGIN{ $orig_can = __PACKAGE__->can("can") }
sub can {
my ($self, $meth) = #_;
# check if we have a special method
my $code = $methods{$meth} if ref $self and $self->{can}{$meth};
return $code if $code;
# check if we have a normal method
return $self->$orig_can($meth);
}
And AUTOLOAD would change to
my ($self) = #_; # do not `shift`
(my $method = our $AUTOLOAD) =~ s/.*://;
my $code = $self->can($method) or die "unknown method $method";
goto &$code; # special goto. This is a AUTOLOAD idiom, and avoids extra call stack frames
Don't do too much magic. I've gotten away from AUTOLOAD because it causes maintenance issues where mysterious methods suddenly appear and disappear.
One way to handle what you want is to define all the methods you need, and if a particular object is of the wrong type, simply cause that method to croak:
sub Foo {
my $self = shift;
my $parameter = shift;
if ( $self->Class_type ne "Foo" ) {
croak qq(Invalid method 'Foo' on object #{[ref $self]});
}
print "here be dragons\";
return "Method 'Foo' successfully called";
}
The above will not allow method Foo to be called unless the class type is Foo.
If your objects won't change (or you don't want them to change) once an object is created, you can define that object as a sub-class.
Before you bless a newly created object, check that special value and decide whether or not you need to create a specific sub-class instead.
package My_class;
sub new {
my $class = shift;
my $class_type = shift;
my $self = shift;
if ( $class_type eq "Foo" ) {
bless $self, "My_class::Foo";
}
else {
bless $self, $class;
}
package My_class::Foo;
use base qw(My_class);
sub Foo {
my $self = shift;
return "Foo Method successfully called!";
}
Notice that my class My_class::Foo is a sub-class of My_class via the use base pragma. That means all methods for My_class are valid with objects of My_class::Foo. However, only objects of My_class::Foo can call the Foo method.
When I create my object (via the new subroutine), I look at the $class_type parameter. If it's a type Foo, I bless the class as My_class::Foo.
Here's an example where I use sub-classes to do what you want.
Every object is a class type of Question. You can see my constructor on line 1129. I pass in a question type as one of the parameters to my constructor.
In line 1174 to 1176, I create my object, but then append the question type to the class, and then bless the question as that sub-class type. All of my subclasses are a type Question (see my use base qw(Question); below each package declaration. However, only questions of sub-class Question::Date and Question::Regex have a method Format. And, only objects of type Question::Words have a method Force.
Hope this helps.
None of the answers so far given actually handle the question actually asked.
Adding methods to an instance in Perl is not directly supported. Object instances are always instances of some class, and that class is the thing that actually has methods. You cannot add a method to a single instance of a class, without making that method also available on every other instance of the same class.
For your problem you have two basic solutions:
Provide the methods always, but test a flag to see whether the method should apply to the given instance or not. This is by far the simplest.
Bless each object into subclasses depending on the flags. Subclass the main class to provide those methods as appropriate.
If you truely want to add methods on individual instances, then what you'll have to do is arrange that every instance is a single instance of a newly-derived class for every object. This gets harder to arrange for, doubly-so if you want to avoid leaking memory and cleaning up the classes once the objects are DESTROYed. This would however allow truely per-instance methods.
Since it is highly unlikely you'll truely need this third option it is far better to go with one of the first.

How do I interact with a Perl object that has a hash attribute?

I have a class with several variables, one of which is a hash (_runs):
sub new
{
my ($class, $name) = #_;
my $self = {
_name => $name,
...
_runs => (),
_times => [],
...
};
bless ($self, $class);
return $self;
}
Now, all I'm trying to do is create an accessor/mutator, as well as another subroutine that pushes new data into the hash. But I'm having a hell of a time getting all the referencing/dereferencing/$self calls working together. I've about burned my eyes out with "Can't use string ("blah") as a HASH ref etc etc" errors.
For the accessor, what is 'best practice' for returning hashes? Which one of these options should I be using (if any)?:
return $self->{_runs};
return %{ $self->{_runs} };
return \$self->{_runs};
Further, when I'm using the hash within other subroutines in the class, what syntax do I use to copy it?
my #runs = $self->{_runs};
my #runs = %{ $self->{_runs} };
my #runs = $%{ $self->{_runs} };
my #runs = $$self->{_runs};
Same goes for iterating over the keys:
foreach my $dt (keys $self->{_runs})
foreach my $dt (keys %{ $self->{_runs} })
And how about actually adding the data?
$self->{_runs}{$dt} = $duration;
%{ $self->{_runs} }{$dt} = $duration;
$$self->{_runs}{$dt} = $duration;
You get the point. I've been reading articles about using classes, and articles about referencing and dereferencing, but I can't seem to get my brain to combine the knowledge and use both at the same time. I got my _times array working finally, but mimicking my array syntax over to hashes didn't work.
You are storing references to array or hashes in your object. To use them with standard functions you'll need to dereference them. For example:
#{ $self->{_array_ref_key} };
%{ $self->{_hash_ref_key} };
If you need pass parameters to standard function:
push( #{ $self->{_array_ref_key} }, $some_value );
for my $hash_key ( keys %{ $self->{_hash_ref_key} }) {
$self->{_hash_ref_key}{$hash_key}; ## you can access hash value by reference
}
Also $self->{_hash_ref_key}{$hash_key} syntax is shortcut for $self->{_hash_ref_key}->{$hash_key} (which can make for sense if you see it first time).
Also take a look at corresponding manual page.
Might as well take my comments and make a proper answer out of it. I'll illustrate exactly why your sample code failed.
use warnings;
my $self = {
_name => $name,
_runs => (),
_times => [],
};
bless ($self, $class);
use Data::Dump::Streamer; DumpLex $self;
__END__
Odd number of elements in anonymous hash at …
$self = bless( {
_name => undef,
_runs => '_times',
"ARRAY(0x88dcb8)" => undef,
}, '…' );
All the elements in the list form the key/value pairs for the hash whose reference is going to be blessed. () is an empty list, so what you're really expressing is the list '_name', $name, '_runs', '_times', []. You can see that _times moves up to become a value, and the reference [] is stringified as hash key. You get the warning because there's no value left for it; this will be automatically coerced to undef. (Always always enable the warnings pragma.)
Now for the guts part: hash values must be a scalar value. Arrays and hashes aren't; but references to them are. Thus:
my $self = {
_name => $name,
_runs => {},
_times => [],
};
First, you have to figure out what you actually want to return and what you want the higher level to be able to do with the data.
If you want to return a copy of the data or any changes to the returned data don't affect the copy in the object, you can't do the simple solutions that the other answers tell you because they return shallow copies which will still share internal references. You need to make a deep copy then return the disconnected data structure. Storable makes this easy with dclone:
use Storable qw( dclone );
sub some_method {
my( $self, ... ) = #_;
...;
my $clone = dclone( $self->{_runs} );
$clone;
}
If you want the higher level to change the object by changing the returned data structure, just return the reference that you already store. You don't need to do anything fancy for that:
sub some_method {
my( $self, ... ) = #_;
...;
$self->{_runs};
}
Beyond that, it's your job to create an interface so that people don't have to think about your data structure at the higher level. You encapsulate everything so your implementation details don't show themselves. That way, you can change the implementation without disturbing the higher level code (as long as the interface is stable).
You create a runs method that returns a list of runs:
sub get_run_keys {
my( $self ) = #_;
keys %{ $self->{_runs} };
}
Or maybe you just want the values:
sub get_run_values {
my( $self ) = #_;
values %{ $self->{_runs} };
}
Or maybe the whole thing:
sub get_run_hash {
my( $self ) = #_;
$self->{_runs}; # subject to the cloning stuff I mentioned earlier
}
When you want to get the values for a particular run, you access it through another method:
sub get_run {
my( $self, $key ) = #_;
$self->{_runs}{$key};
}
Setting a run value is similar:
sub set_run {
my( $self, $key, $value ) = #_;
$self->{_runs}{$key} = $value;
}
Now your higher level doesn't know anything about the infrastructure, and the method names describe what you are trying to do instead of how the infrastructure has to do it:
foreach my $key ( $self->get_run_keys ) {
my $run = $self->get_run( $key );
...;
$self->set_run( $key, $new_value );
}
Object-oriented design is a big topic, and there is a lot you can do. This is just enough to get you started. You can wrap other operations too:
sub does_run_exist {
my( $self, $key ) = #_;
exists $self->{_runs}{$key};
}
sub delete_runs {
my( $self, #keys ) = #_;
delete $self->{_runs}{$key} foreach my $keys ( #keys );
}
sub reset_runs {
my( $self, $key ) = #_;
$self->{_runs} = {};
}