7

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?

brian d foy
  • 129,424
  • 31
  • 207
  • 592
sgsax
  • 393
  • 1
  • 4
  • 8
  • 1
    Everyone seems to want to make there lives harder by turning two perfectly good lines into one messed up line. Surely there are better things to think about. :) – brian d foy Feb 13 '10 at 19:50

5 Answers5

9

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
  • 116,958
  • 15
  • 196
  • 339
  • I don't get what the + is used for here. I appreciate the education. Thanks. – sgsax Feb 12 '10 at 22:12
  • 3
    +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 Feb 12 '10 at 22:15
  • 2
    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 Feb 12 '10 at 22:15
6

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
  • 39,821
  • 2
  • 80
  • 152
  • That gets me exactly what I was looking for while maintaining my existing class code. Thanks! – sgsax Feb 12 '10 at 22:03
2

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.

mtmk
  • 6,176
  • 27
  • 32
  • 2
    the hash is turned into a list, not an array. the distinction is subtle but important. – Eric Strom Feb 12 '10 at 22:01
  • 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 Feb 12 '10 at 22:05
  • Thanks Eric. I meant to write LIST but it's been a while. My Perl is quite rusty now. – mtmk Feb 12 '10 at 22:08
  • 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 Feb 12 '10 at 22:13
  • 1
    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. – mtmk Feb 12 '10 at 22:17
  • 1
    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 Feb 12 '10 at 22:18
1

Just return a hashref instead of a hash:

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

DVK
  • 126,886
  • 32
  • 213
  • 327
0

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
  • 129,424
  • 31
  • 207
  • 592