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

I'm just getting started into creating perl modules and have been studying other's code to help get the hang of it. Recently, I ran across a subroutine that returns an array in such a way that the subroutine looks like a property instead of a method and can be used in a foreach call. I have come across a need to do this with a hash, but haven't been able to come up with a working syntax. Is this even possible, and if so, how? Here's what I've tried:
#In module: sub acls { my $self = shift; my %acls = \$self->{ACLS} # $self->{ACLS} is a hashref return %acls; } #using module: my $mod = new Module; while(my ($name, $value) each $mod->acls) { print $name; }
I've also tried this by using:
foreach(keys $mod->acls) { }
Thanks for any advice on this.

Replies are listed 'Best First'.
Re: Hashes as return values
by blazar (Canon) on Jan 17, 2006 at 16:02 UTC

    You can either return a reference to a hash, which probably you don't since you assign to %acls which happens to be a hash, not a hashref, or return a flattened list, then assign it to a hash. But it seems that it is not what you want. Just remember that just like an array is not a list, and the difference is (in a technically imprecise terminology) fundamentally that "the former has a name", the same happens with hashes: a hash in list context is flattened. OTOH each and keys expect a hash, so you must give them a hash, full of its name. But if it is anonymous, then call it by its ehm anonymous name, by dereferencing:

    #!/usr/bin/perl -l use strict; use warnings; package Module; sub new { bless { ACLS => {foo => 1, bar => 2} }, shift; } sub acls { my $self=shift; $self->{ACLS} } package main; my $mod=Module->new; while (my @stuff=each %{ $mod->acls } ) { print "@stuff"; } __END__
Re: Hashes as return values
by mrborisguy (Hermit) on Jan 17, 2006 at 15:41 UTC

    I believe that my %acls = \$self->{ACLS}; is making a reference to the hashref. You would want something like my %acls = %{ $self->{ACLS} }; instead.

        -Bryan

      Thanks. However, that didn't quite get it. I made the above change and then tried the following:
      foreach(keys $config->acls) { print "$_"; }
      and
      while(my ($name, $value) = each $config->acls()) { print "$name<br>$value<br>"; }
      But both returned the error:
      "Type of arg 1 to keys must be hash (not subroutine entry)"
        The error means exactly what it says, 'keys' needs a hash, not a hashref, or anything else. This works:

        use strict; use warnings; my $obj = Foo->new(); for my $key (keys %{$obj->{acls}}) { print "$key\n"; } package Foo; sub new { my $self = shift; my %obj = ( acls => { a=>1, b=>2 } ); return bless \%obj; }

        Note the %{} round $obj->{acls} dereferencing the hashref into a hash.

        Update: Just as added clarification, since you were talking about return values from functions:

        use strict; use warnings; my $obj = Foo->new(); for my $key (keys %{$obj->{acls}}) { print "$key\n"; } for my $key (keys %{$obj->acls}) { print "$key\n"; } package Foo; sub new { my $self = shift; my %obj = ( acls => { a=>1, b=>2 } ); return bless \%obj; } sub acls { my $self = shift; return \%{$self->{acls}}; }

        The second for loop calls acls() to get at the content of the attribute {acls}, dereferences it, and iterates through printing the keys. The first loop is accessing the hashref directly, rather than through a function call.

        --------------------------------------------------------------

        "If there is such a phenomenon as absolute evil, it consists in treating another human being as a thing."

        John Brunner, "The Shockwave Rider".

Re: Hashes as return values
by thedoe (Monk) on Jan 17, 2006 at 16:06 UTC

    If $self->{ACLS} is a hashref, then one way to correct this is to return the hashref directly and cast it into a hash in your loop declaration:

    sub acls { my $self = shift; # my %acls = \$self->{ACLS} # $self->{ACLS} is a hashref # return %acls; return $self->{ACLS}; } foreach(keys %{$mod->acls}) { # code in here }

    You must make sure to include the extra braces around $mod->acls or it will try to interpret %$mod before $mod->acls. Hope this helps!

Re: Hashes as return values
by NetWallah (Canon) on Jan 17, 2006 at 20:00 UTC
    Here is yet another way to do this, using callbacks. It simplifies client programming, but requires a coderef to be passed into the method call.
    #!/usr/bin/perl -l use strict; use warnings; package Module; sub new { bless { ACLS => {foo => 1, bar => 2} }, shift; } sub acls { my ($self , $callbackref) = @_; while ( my ($k,$v) = each %{ $self->{ACLS} }){ &$callbackref($k,$v); } } package main; my $mod=Module->new; $mod->acls ( sub{ print "@_\n"; } );
    ---Output---
    bar 2 foo 1

         You're just jealous cause the voices are only talking to me.

         No trees were killed in the sending of this message.    However, a large number of electrons were terribly inconvenienced.

Re: Hashes as return values
by dirtdart (Beadle) on Jan 17, 2006 at 16:26 UTC
    Thanks for all the great wisdom! Everything works almost perfectly now. One other quick question if you don't mind, though. Is there any prettier syntax than:
    %{ $config->acls }->{$_}
    to return the value during the foreach loop? Or should I just stick with the while loop if I want pretty code? Thanks again!

      If you would like something more pretty, I would recommend assigning the return value into a hashref then looping over that:

      my $loopHashRef = $mod->acls; foreach (keys %$loopHashRef) { print $loopHashRef->{$_}; }

      If you like, you could also assign the return value directly into a hash and loop over that:

      my %loopHash = %{$mod->acls}; foreach (keys %loopHash) { print $loopHash{$_}; }

      Update: Fixed a sigil - *grumble* copy+paste mistakes... *grumble*

      I wouldn't have even expected that to work. Briefly: $config->acls returns a hashref. So far so fine. You can dereference it with

      my %hash = %{ $config->acls }; # to get a full hash, # and my $item = $config->acls->{thatkey}; # to get a single element.

      Now the first element is possibly a bit deceiving, because it would work even if the hashref was "converted" to the flattened list of its pairs, whereas the rvalue is a hash at all effects, so that it can directly used e.g. with each and keys, like it was already explained to you.

      Whatever, accessing elements as per the second example, still involves method calls, and although I, like so many others, recommend all the time not to bother about alleged optimizations, that feels somewhat unsatisfactory. Personally, I'd just assign to a suitably lexically scoped hash say, %tmp for convenience. YMMV.

Re: Hashes as return values
by Sioln (Sexton) on Jan 17, 2006 at 15:56 UTC