9

I'm sure this is covered in the documentation somewhere but I have been unable to find it... I'm looking for the syntactic sugar that will make it possible to call a method on a class whose name is stored in a hash (as opposed to a simple scalar):

use strict; use warnings;

package Foo;
sub foo { print "in foo()\n" }

package main;
my %hash = (func => 'foo');

Foo->$hash{func};

If I copy $hash{func} into a scalar variable first, then I can call Foo->$func just fine... but what is missing to enable Foo->$hash{func} to work?

(EDIT: I don't mean to do anything special by calling a method on class Foo -- this could just as easily be a blessed object (and in my actual code it is); it was just easier to write up a self-contained example using a class method.)

EDIT 2: Just for completeness re the comments below, this is what I'm actually doing (this is in a library of Moose attribute sugar, created with Moose::Exporter):

# adds an accessor to a sibling module
sub foreignTable
{
    my ($meta, $table, %args) = @_;

    my $class = 'MyApp::Dir1::Dir2::' . $table;
    my $dbAccessor = lcfirst $table;

    eval "require $class" or do { die "Can't load $class: $@" };

    $meta->add_attribute(
        $table,
        is => 'ro',
        isa => $class,
        init_arg => undef,  # don't allow in constructor
        lazy => 1,
        predicate => 'has_' . $table,
        default => sub {
            my $this = shift;
            $this->debug("in builder for $class");

            ### here's the line that uses a hash value as the method name
            my @args = ($args{primaryKey} => $this->${\$args{primaryKey}});
            push @args, ( _dbObject => $this->_dbObject->$dbAccessor )
                if $args{fkRelationshipExists};

            $this->debug("passing these values to $class -> new: @args");
            $class->new(@args);
        },
    );
}

I've replaced the marked line above with this:

        my $pk_accessor = $this->meta->find_attribute_by_name($args{primaryKey})->get_read_method_ref;
        my @args = ($args{primaryKey} => $this->$pk_accessor);

PS. I've just noticed that this same technique (using the Moose meta class to look up the coderef rather than assuming its naming convention) cannot also be used for predicates, as Class::MOP::Attribute does not have a similar get_predicate_method_ref accessor. :(

Ether
  • 53,118
  • 13
  • 86
  • 159
  • I don't think that is possible due to Perl's parsing order. Why do you not want copy $hash{func} into a scalar first? – Mikael S Dec 02 '09 at 22:16
  • No particular reason except it seems unnecessary, and this was an interesting puzzle that stumped me. I did not believe that simply because I did not know the answer that there was no answer. :) (tl;dr version: because I'm curious!) – Ether Dec 02 '09 at 22:22
  • Er, it seems to me that if you're using Moose, then you're still going about it the wrong way. One of the features of Moose is the meta object model that it's built on...I have a feeling that there's a method you can call to look up the actual sub by string name, which you could then call, instead of using bare strings. I don't know it off the top of my head, though... – Robert P Dec 03 '09 at 01:53
  • @RobertP: I think you mean `my $reader_ref = $this->meta->find_attribute_by_name($fieldname)->get_read_method_ref;` (see Class::MOP::Class and Class::MOP::Attribute), but all this does is get around the possibility that the reader is named something different than the attribute name. – Ether Dec 03 '09 at 03:27
  • PS. tomorrow I'll post the *actual* code that I was dealing with so we can critique it more :) there may indeed be a better way of doing what I was doing. It is mostly orthogonal to my curiosity over this particular question of syntax, however. – Ether Dec 03 '09 at 03:29
  • Yeah, I thought so. For others who look at this piece, once you have that, you can just do `$this->$reader_ref()` and it will be way cleaner than the by-string-name version. PBP has a good explanation why using a subref is the better way of doing it, if anyone is interested. – Robert P Dec 03 '09 at 17:17
  • 1
    As a historical footnote, I should note now that all the code above got nuked from orbit in a subsequent refactoring. 1. it's crazy to attempt to reimplement an ORM when there are many good ones to choose from, and 2. in Moose, it's better to call `$attr->set_value($instance, $value)` rather than making assumptions about what an accessor is called. – Ether Jun 07 '10 at 20:52
  • I assume you meant Moose::Exporter in "Edit 2". – Christopher Bottoms Jun 10 '10 at 12:50

3 Answers3

15
Foo->${\$hash{func}};

But for clarity, I'd probably still write it as:

my $method = $hash{func};
Foo->$method;
runrig
  • 6,486
  • 2
  • 27
  • 44
  • Simple *and* obvious; I love it! – Ether Dec 02 '09 at 22:58
  • I understand the first dollar sign. But what I don't get is the backslash. – innaM Dec 03 '09 at 11:55
  • 1
    @Manni: The ${...} is parsed as a scalar dereference rather than a disambiguation because the contents aren't a literal string. The backslash is to create a reference to the value of $hash{func}. – Michael Carman Dec 03 '09 at 13:41
  • 1
    Manni's comment is an excellent example of why you shouldn't use this construct in production code. It's not as simple as it appears at first glance. How it works (and by extension, what it does when you return to it a year from now) can be cryptic even to experienced Perl programmers. It's much better write *clear* code than to write *clever* code: `my $method = $hash{func}; Foo->$method()`. – Michael Carman Dec 03 '09 at 14:22
  • I mostly agree with Michael, so I've updated my answer to reflect that. I've never actually used the shorter way except to see that it works, but I've used the longer way many times. – runrig Dec 03 '09 at 16:43
  • Oh interesting. Is the Foo->$method syntax documented in the official doc? I've looked around and can't seem to find it. – solstice333 Sep 27 '16 at 00:35
2

Is there a reason you are storing subroutine names instead of the references to code?

e.g.

use strict; use warnings;

package Foo;
sub foo { print "in foo()\n" }

package main;
my %hash = (func => \&Foo::foo);

$hash{func}->();

You won't be passing the class name, but if that's important to you, you can use something like

my %hash = ( func => sub { return Foo->foo(@_) } );
jsoverson
  • 1,695
  • 1
  • 12
  • 17
  • Yes, because the sub names correspond to attribute names taken from Moose objects. – Ether Dec 02 '09 at 22:16
  • I've edited my question to indicate that `Foo` could either be a class name or a blessed object. – Ether Dec 02 '09 at 22:17
  • You don't want a sub reference because that breaks inheritance. – brian d foy Jan 26 '11 at 19:03
  • So, instead of a sub reference use a closure over the methodname, like my %hash = (func => foo); my %method_hash = map { my $meth = $hash{$_}; $_ => sub { Foo->$meth } } keys %hash; $method_hash{func}->(); – MkV Feb 10 '12 at 19:36
1

Have you tried UNIVERSAL's can method? You should be able to implement something like this:

## untested
if ( my $code = $object->can( $hash{func} ) ) {
    $object->$code();
}

I made a useless, one-line example to demonstrate:

perl -MData::Dumper -le 'my %h = ( f => "Dump" ); my $o = Data::Dumper->new( [qw/1 2 3/] ); my $ref = $o->can( $h{f} ); print $o->$ref()'
gpojd
  • 22,558
  • 8
  • 42
  • 71
  • Yes, but again this is a diversion away from my original question about how to use a hash value as a method name (which runrig answered). `can` in this case is a less perfect solution than the one I outlined in a question edit, as it makes assumptions about the naming of a Moose reader. – Ether Dec 03 '09 at 20:52