tags:

views:

141

answers:

3

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.

+2  A: 

You are storing references to array or hashes in your object. To you them with standard functions you'll need to derefererence 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.

Ivan Nevostruev
Thanks, this combined with daxim's comment above was exactly what I needed.
brydgesk
+3  A: 

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 => [],
};
daxim
Very useful, thanks for the help. I just knew that I usually initialize hashes as "%var = ()", so I assumed (I know, I know) that the constructor should use the same thing.Don't think I could have found that on my own, thanks again.
brydgesk
You declare hashes with just `my %var;`. The assignment of an empty list is totally superfluous.
daxim
A: 

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} = {};
      }
brian d foy