views:

167

answers:

6

I have a class with a method that returns a hash. Ordinarily, I would get the result like so:

%resp = $myclass->sub($foo);

And then access members of the returned hash like this:

$resp{key}{subkey};

in the case of a 2d hash.

I figure there must be a way to combine this into a single, elegant line, something like this:

$myclass->sub($foo)->{key}{subkey}

This obviously is not dereferenced properly as Perl returns this when trying to run the code:

Can't use string ("1/8") as a HASH ref

In trying random dereferencing sequences, from looking "References quick reference" on Perlmonks, I came up with the following, which Perl does not complain about, but also does not return what I'm looking for:

$%{$myclass->sub($foo)}->{key}{subkey}

Can somebody tell me what the magic dereferencing escape sequence is to do this?

+1  A: 

Just return a hashref instead of a hash:

$myclass->sub($foo)->{key}->{subkey}

DVK
Same result as with the -> before {key}. Thanks.
sgsax
Same result also if I take out -> altogether.
sgsax
Whazzup with -1? Second solution worked.
DVK
@DVK who knows?
Sinan Ünür
@DVK, Wasn't me. I'm taking it all in right now, haven't scored anything.
sgsax
+2  A: 

I'd return a HASH reference from the sub instead. Otherwise (probably) your hash is turned into a LIST then into a HASH again for no reason:

sub mysub() {
  ...
  return \%myhash;
}

There is less copying involved when returning the reference hence more efficient.

Maxwell Troy Milton King
the hash is turned into a list, not an array. the distinction is subtle but important.
Eric Strom
I can make that work. Can you explain the advantage of returning a hashref instead of a hash? I can rework the other methods in the class easily enough. Thanks!
sgsax
Thanks Eric. I meant to write LIST but it's been a while. My Perl is quite rusty now.
Maxwell Troy Milton King
@sgsax => see the edit to my answer
Eric Strom
Scope isn't an issue then? Since the sub is a method from the external class, as long as the class object is alive the scope of the hashref is still good? I'm still trying to grok this. Thanks.
sgsax
No. Your referenced objects live as long as they are referenced. However make sure not to create circular references since Perl's GC works by reference counting.
Maxwell Troy Milton King
once the hashref is returned, a reference to it exists in the calling scope which preserves the value. Perl uses a reference count based garbage collection system, so as long as one reference to the value exists, it will not be cleaned up. once the returned value falls out of scope (if it wasn't copied anywhere else), then Perl will free the memory
Eric Strom
+5  A: 

Changing the sub to return a hash reference would work best, but to get the functionality you are looking for:

{ $myclass->sub($foo) }->{key}{subkey}

which will create a hash reference from the list returned by sub, and then immediately dereference it

Edit: The advantage to returning a hashref from your sub rather than a hash (as a list) is largely one of performance. In the hashref case, the hash is created once, and then accessed. In the list case, the hash is created, then converted into a list, then created again, and then dereferenced. For small hashes this wont take too much time, but for larger ones, it is just unnecessary work for the runtime. Basically, if you plan to use the data as a list in some places, and as a hash in others, returning the hash as a list is ok. But if you are always planning to use it as a hash, returning the reference is clearer and faster.

Eric Strom
That gets me exactly what I was looking for while maintaining my existing class code. Thanks!
sgsax
+8  A: 

What you are trying to do is neither elegant nor advisable. You have somehow managed to invoke the routine in scalar context (that's what the "1/8" corresponds to).

Return a hash reference.

Now, take a look at:

#!/usr/bin/perl

package My::Mine;

use strict; use warnings;

sub test {
    my %h = (
        a => { z => 1},
        b => { y => 2},
    );
    return %h;
}

package main;

use strict; use warnings;

my $class = 'My::Mine';

print { $class->test }->{a}{z}, "\n";

That will not work. Instead, you will have to do:

print +{ $class->test }->{a}{z}, "\n";

Now, that's elegant (Not!) See perldoc -f print.

Long story short, return a reference to the hash.

Note that the fresh anonymous hash you are constructing is not free. Nor is the cost of returning a hash as a flat list from a subroutine.

Sinan Ünür
I don't get what the + is used for here. I appreciate the education. Thanks.
sgsax
+1 for explaining 1/8 clearly. Also, one more drawback to returning a hash - it lulls you into a false sense of security if you think "this is a copy, so I can safely modify it" - yet, the internals of the data structure are still refs that are not deep cloned so you can not safely modify them.
DVK
the + is a hint to the parser that the {...} construct following the print is not an indirect object for the print method. this is used in situations like `print {sub_returns_a_file_handle()} "stuff to print - note that there is not a comma before the open quote";`
Eric Strom
+1 for pointing out the scalar context issue.
Maxwell Troy Milton King
@Eric, Got it, thanks!
sgsax
A: 

Thanks to all who responded on this one. I appreciate the detailed answers on use of hashrefs. I'll be reworking my classes to return hashrefs instead of hashes. I appreciate all the info. I'm just starting to get comfortable with perl, but there's a whole lot to take in and I'm sure I'm just barely skimming the surface.

sgsax
Do not post non-answers as answers. You can edit the question or use comments.
Sinan Ünür
A: 

If you always want to just get one subkey, write a short subroutine to do that for you and forget about golfing your line. One advantage is that you can easily add some sanity checking:

 sub get_subkey {
      my( $class, $arg, $key, $subkey ) = @_;

      my $hashref = $class->method( $arg );
      return unless ref $hashref;

      return $hashref->{$key}{$subkey};
      }
brian d foy