zzspectrez has asked for the wisdom of the Perl Monks concerning the following question:

OK. Once again... Im pulling my hair out trying to figure something out. Im starting to play with making my own packages/modules. One of the benefits I am seeing is the ability to encapsulate your data within the module and only access the data through the modules routines. This is great.

In the process of playing around with this, I have come across the problem of how to return a hash that contains reference to arrays. Im still learning about using references so Im sure I just have something mixed up. Here is some example code similar to what Im doing with most of the unimportant stuff removed.

The module

package TestPackage; sub new () { my $class = shift; my %opts = @_; my $self = {}; if (exists($opts{'username'}) && (exists($opts{'password'}))){ $self->{'username'} = $opts{'username'}; $self->{'password'} = $opts{'password'}; bless $self, $class; }else{ return undef; } $self->_init_Authentication; return $self; } sub Identities () { my $self = shift; if (exists($self->{'identities'})) { return @{$self->{'identities'}}; }else { return undef; } } sub BuddyList () { my $self = shift; if (exists($self->{'buddy_list'})){ return %{$self->{'buddy_list'}}; }else{ return undef; } } sub _init_Authentication () { my $self = shift; ## ---- snip lots of code ---- ## assume $buddylist = "group1:name1,name2\ngroup2:name2,name3\n.. +.etc..." my %buddylist; my @groups = split /\n/, $buddylist; foreach (@groups) { my ($group,$names) = /^(.+?):(.+)/; $buddylist{$group} = [ split /,/, $names ]; } # ---- snip more code ---- # assume $myident = myname1,myname2, etc... my @identities = split /,/, $myident; $self->{'buddy_list'} = \%buddylist; $self->{'identities'} = \@identities; } 1;

The test script

#!/usr/bin/perl -w; use strict; use TestPackage; my $p = TestPackage->new ( username => 'zzspectrez', password => 'perlkicksbutt' ) || die "Couldn't create object.\n"; print "Identities: ", join (' ', $p->Identities), "\n"; #my %buddys = $p->BuddyList; my $buddys = $p->{'buddy_list'}; foreach my $key (keys %$buddys){ print "GROUP: $key\n"; print "NAMES: ", join (' ', @{$$buddys{$key}}), "\n"; }

The following works. But I cant seem to create a method to return the the hash properly. The $p->BudyList routine doesnt work. Instead I have to access the data directly, which I would rather not do. What am I doing wrong?!?!? I even tried having it return a hard coded hash.

Thanks!
zzSPECTREzz

Replies are listed 'Best First'.
Re: Packages, references, & data hiding...
by Fastolfe (Vicar) on Dec 03, 2000 at 22:27 UTC
    On a side note, this might not do what you expect:
    return @{$self->{'identities'}}; } else { return undef;
    Since you seem to be pulling the results of this in a list context, your resulting array will contain something like this:
    @success = ($identity1, $identity2, $identity3); @failure = (undef);
    Either way, a test of @array in a scalar context is going to get you a non-zero/true value either way. Get into the habit of calling return witn no arguments when you want to return a 'false' value. This will return undef in a scalar context and an empty list in an array context, giving you the results you're looking for.
    } else { return;

      Ahhh.. Thank you for that tidbit. That would introduce a subtle bug. So the way I had it setup it would return true because in array context it would still have one element which happens to be the undef value..

      THANKS!!!!
      zzSPECTREz

Re: Packages, references, & data hiding...
by repson (Chaplain) on Dec 03, 2000 at 16:54 UTC
    Okay what you are asking is fairly simple once you think about what you want.
    What you are doing is something like this:

    $buddys = %data;

    This is not what you want at all. What you actually want is a scalar which is a reference to %data to be stored in $buddys which you can then dereference in your loop.
    This means you want the equivelent of this:

    $buddys = \%data;

    In _init_Authentication you are already doing half of this ie:

    $self->{'buddy_list'} = \%buddylist;

    And then in BuddyList, $self->{'buddy_list'} still contains a reference to the hash you want as it does when you try using directly in main. So all you need to return is $self->{'buddy_list'} which then gets put in $buddys and can be used as you wish.

    Hope that was what you wanted to know.

      What I was hoping for, is a way to return the hash of buddys instead of the reference to the hash. Sort of how the Identites method returns the actual array instead of the reference to one.

      Then I could do something like:

      my %buddys = $p->BuddyList; foreach my $key (keys %buddys) { print "GROUP: key\n"; print "NAMES: ", join (' ', @{$buddys{$key}}), "\n"; }

      I guess it is about the same, just take me one step closer to the data and remove the use of one $

      Thanks for the suggestions.
      zzSPECTREz

        It is quicker to return a hash ref, and if you have the hash ref you can still manipulate that hash...just do it like this:

        my $buddys = $p->BuddyList; #this returns a hash ref
        for (keys %{$buddys})
        {
            print "GROUP: $_\n";  #I'm asuming that 'keys' here was a reference to $keys in your foreach...
            print "NAMES: ", join (' ', @{$buddys->{$_}}), "\n";  #similarly, I removed $keys and so I'm using $_
        }
        

        This gives you the speed of using a reference (incedentally, for only a few pieces of punctuation more, you can do the same thing with Indenties and use an arrayref), along with the ease of an actual hash (in the end, that's what it is). you could feasibly get away with

        foreach ( keys %$buddys )
        But I always find it safer to wrap the ref in braces.

        Note that, in general, if foo returns a hashref, you can get the hash (if you *really* need it) by using braces judiciously, e.g.

        my %hash = %{foo(@args_to_foo)}; # or, with a method that returns a hashref my %hash = %{$object->foo};

        Either way, though, you lose some of the speed advantages of passing around references.

        Philosophy can be made out of anything. Or less -- Jerry A. Fodor

        There is one other thing you need to consider between return a hash or hash-ref. Do you want the original data editable?
        Look at this code:
        sub hash { my $h = shift; return %$h; } sub href { my $h = shift; return $h; } my $n = { moo => 'blah', arrg => 'no' }; my %hash = hash($n); $hash{arrg} = 'yes'; print join(' ',%$n) . "\n"; # Output: moo blah arrg no my $m = { moo => 'blah', arrg => 'no' }; my $hash = href($m); $hash->{arrg} = 'yes'; print join(' ',%$m) . "\n"; # Output: moo blah arrg yes
        The first one is what you were doing originally, I don't know why it didn't work. But it is better to return a hash-ref unless you really need to protect the original data (which anyone can get to with $p->{buddys_list} anyway if they really want to).